Apple data using SimpleTidy Workflow
Here we use another data set trying to go through the same
workflow.
Package requirement
library(svglite)
library(limma)
library(tidyverse)
── Attaching core tidyverse packages ─────────────────────────────────────────────────────────────────────────────────────────────────── tidyverse 2.0.0 ──
✔ dplyr 1.1.4 ✔ readr 2.1.5
✔ forcats 1.0.0 ✔ stringr 1.5.1
✔ ggplot2 3.5.1 ✔ tibble 3.2.1
✔ lubridate 1.9.3 ✔ tidyr 1.3.1
✔ purrr 1.0.2
── Conflicts ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────── tidyverse_conflicts() ──
✖ dplyr::filter() masks stats::filter()
✖ dplyr::lag() masks stats::lag()
ℹ Use the ]8;;http://conflicted.r-lib.org/conflicted package]8;; to force all conflicts to become errors
library(ggplot2)
library(igraph)
Attaching package: ‘igraph’
The following objects are masked from ‘package:lubridate’:
%--%, union
The following objects are masked from ‘package:dplyr’:
as_data_frame, groups, union
The following objects are masked from ‘package:purrr’:
compose, simplify
The following object is masked from ‘package:tidyr’:
crossing
The following object is masked from ‘package:tibble’:
as_data_frame
The following objects are masked from ‘package:stats’:
decompose, spectrum
The following object is masked from ‘package:base’:
union
library(ggraph)
library(readxl)
library(patchwork)
library(RColorBrewer)
library(viridis)
Loading required package: viridisLite
Data resource
Malus domestica. The data frame contains microarray gene expression
data, with each row corresponding to a gene and each column representing
expression levels at different time points. The data includes the mean
(Mean), number of data points (n), and standard error (SE). For the 0
DAA time point, only one biological replicate was sampled. For other
time points, the data is based on two biological replicates and two
technical replicates. In some cases, data points were excluded for
technical reasons, so the mean and standard error are only calculated
based on the remaining data.
apple_exp = read_excel("Apple_DAA_expression.xls",col_types = "text")
dim(apple_exp)
[1] 15987 26
There are 15987 genes and 8 time point, DAA stands for Days After
Anthesis.
# general check on the df info
apple_exp %>%
mutate(across(ends_with("Mean"), as.numeric)) %>%
select(ends_with("Mean")) %>%
summary()
0 DAA Mean 14 DAA Mean 25 DAA Mean 35 DAA Mean 60 DAA Mean 87 DAA Mean 132 DAA Mean 146 DAA Mean
Min. : 0.0970 Min. : 0.08612 Min. : 0.08957 Min. : 0.08781 Min. : 0.1013 Min. : 0.0738 Min. : 0.08753 Min. : 0.06487
1st Qu.: 0.3528 1st Qu.: 0.36176 1st Qu.: 0.35577 1st Qu.: 0.35565 1st Qu.: 0.3618 1st Qu.: 0.3710 1st Qu.: 0.36312 1st Qu.: 0.38478
Median : 0.6993 Median : 0.71192 Median : 0.70186 Median : 0.71305 Median : 0.7226 Median : 0.7375 Median : 0.71482 Median : 0.75483
Mean : 3.3384 Mean : 3.33391 Mean : 3.31275 Mean : 3.44957 Mean : 3.4013 Mean : 3.4371 Mean : 3.32117 Mean : 3.36191
3rd Qu.: 2.0028 3rd Qu.: 2.02268 3rd Qu.: 2.02592 3rd Qu.: 2.05144 3rd Qu.: 2.0625 3rd Qu.: 2.0951 3rd Qu.: 1.99989 3rd Qu.: 2.14520
Max. :73.1040 Max. :95.82664 Max. :100.00000 Max. :73.81931 Max. :78.6526 Max. :61.7287 Max. :77.80648 Max. :68.08869
NA's :442 NA's :64 NA's :115 NA's :196 NA's :175 NA's :319 NA's :130 NA's :193
Usually, when the missing values were caused by very low expression
values or not detected, they were generally replaced by 0. However,
according to the article, the NA part of this data frame was excluded
for technical reasons, so here we employed an exclusion strategy, i.e.,
we did not select the genes containing the missing values for the
subsequent analysis.
apple_exp = apple_exp %>%
mutate(across(ends_with("Mean"), as.numeric)) %>%
select(`Genbank number`,ends_with("Mean")) %>% # SE and n is good to know, but not for subsequent analysis
drop_na()
dim(apple_exp) # 836 genes excluded, 5%. Validation required later.
[1] 15151 9
write.table(apple_exp %>%
distinct(`Genbank number`), "genbank_apple.csv", row.names = FALSE, col.names = FALSE, quote = FALSE)
PCA
ggplot(apple_exp, aes(x = `0 DAA Mean`)) +
geom_histogram(bins = 30)
ggsave("hist_before.svg", height = 3, width = 4, bg ="white")
ggsave("hist_before.png", height = 3, width = 4, bg ="white")

# dev.off()
apple_exp_log = apple_exp %>%
mutate(across(ends_with("Mean"), ~ log10(. + 1))) # log transform the df
ggplot(apple_exp_log, aes(x = `0 DAA Mean`)) +
geom_histogram(bins = 30) # The data distribution is still skewed due to the low expression values of most genes, but it is better than using raw values
ggsave("hist_after.svg", height = 3, width = 4, bg ="white")
ggsave("hist_after.png", height = 3, width = 4, bg ="white")

apple_pca = prcomp(t(apple_exp_log[, -1]))
apple_pca_imp = as.data.frame(t(summary(apple_pca)$importance))
apple_pca_coord = apple_pca$x[, 1:8] %>% # Take the all 8 PCs
as.data.frame() %>%
mutate(timepoint = row.names(.)) # rownames to col
apple_pca_coord
According to the paper, DAA and the fruit development stage have a
certain corresponding relationship:
0-35 DAA : Cell division 60-87 DAA : Starch accumulation 132-146 DA :
Ripening
As we don’t have many combination of the libraries, we won’t expect a
strong explanation by stage but it worth a look.
# mutate a stage column
apple_pca_coord = apple_pca_coord %>%
mutate(stage = case_when(
timepoint %in% c("0 DAA Mean", "14 DAA Mean", "25 DAA Mean", "35 DAA Mean") ~ "Cell division",
timepoint %in% c("60 DAA Mean", "87 DAA Mean") ~ "Starch accumulation",
timepoint %in% c("132 DAA Mean", "146 DAA Mean") ~ "Ripening",
TRUE ~ "Unknown"
)) %>%
mutate(timepoint = str_replace(timepoint, " Mean", "")) # Remove the "Mean" word in timepoint col
apple_pca_coord %>%
ggplot(aes(x = PC1, y = PC2)) +
geom_point(aes(fill = stage), color = "grey20", shape = 21, size = 3, alpha = 0.8) +
scale_fill_manual(values = c("Cell division" = "#1b9e77", "Starch accumulation" = "#d95f02", "Ripening" = "#7570b3")) +
labs(x = paste("PC1 (", apple_pca_imp[1, 2] %>% signif(3)*100, "% of Variance)", sep = ""),
y = paste("PC2 (", apple_pca_imp[2, 2] %>% signif(3)*100, "% of Variance)", " ", sep = ""),
fill = "stage") +
theme_bw() +
theme(
text = element_text(size= 14),
axis.text = element_text(color = "black")
)
ggsave("pca_apple.svg", height = 4, width = 8, bg ="white")
ggsave("pca_apple.png", height = 4, width = 8, bg ="white")

Since PCA is performed based on gene expression data at different
time points, this naturally causes different developmental stages to
show some separation in PCA space. However, PCA itself is unsupervised
and does not know the order of time points, while we see the ordering of
PC1 is consistent with the order of time points, and the interval
between different stages is obvious. This suggests that the closer the
time is to late development, the expression pattern will gradually
change, and PCA has captured this feature.
Bait gene
As the rows in the matrix are not actual gene names, and I didn’t map
the probes to the genes, it’s hard to get the bait genes. However, the
paper provide three core cell cycle genes, which are: EB107042: CDKB1;2
homologue, CN943384: CDKB2;2 homologue, EB141951: CKS1 homologue
We’ll use this three to serve as bait gene
bait_genes = c("EB107042","CN943384","EB141951")
Duplicated genes
apple_exp_log %>%
dim()
[1] 15151 9
apple_exp_log %>%
distinct(`Genbank number`) %>%
dim()
[1] 14902 1
We notice that 15151 > 14902. There must be duplicated
combination:
The reason for this “duplication” is that we deleted the ESTs
(Expressed Sequence Tags) column at the beginning of the data processing
and used the Genbank number as the gene identification.
This may represent different fragments or transcripts of the same
gene. We should alter based on the gene identification to differentiate
them.
apple_exp_log = apple_exp_log %>%
group_by(`Genbank number`) %>%
mutate(gene = case_when( # mutate a new col
n() > 1 ~ paste0(`Genbank number`, "_", row_number()), # "duplicates" will have more than 1 row. Add a symbol.
TRUE ~ `Genbank number` # keep the same for those not_duplicated
)) %>%
ungroup() # %>%
# filter(gene != `Genbank number`) # how to check for the altered rows
Gene selection
# long version
apple_exp_log_long = apple_exp_log %>%
rename_with(~ gsub(" Mean", "", .), contains("DAA")) %>% # Remove the "Mean" in colnames
pivot_longer(
cols = starts_with("0 DAA"):starts_with("146 DAA"),
names_to = "timepoint",
values_to = "log_exp"
)
As we know, the number of correlations scales to the square of number
of genes. In order to calculate gene correlation between each other,
15000+ genes are too much. We can select only the high variance genes,
as a gene is unlikely to be involved in a particular stage if it’s
expressed at a similar level across all timepoints.
# Calculate the rank
apple_var_rank = apple_exp_log_long %>%
group_by(gene) %>%
summarise(var = var(log_exp)) %>% # calculate the variance for each gene
ungroup() %>%
mutate(rank = rank(var, ties.method = "average")) # rank the genes
head(apple_var_rank)
# If we take the top 1/3 of the highest variance genes
high_var_apple = apple_exp_log_long %>%
group_by(gene) %>%
summarise(var = var(log_exp)) %>%
ungroup() %>%
filter(var > quantile(var, 0.667))
# And take the top 3000
high_var_apple3000 = high_var_apple %>%
slice_max(order_by = var, n = 3000)
bait_var = apple_var_rank %>%
filter(gene %in% bait_genes)
# Check whether top 3000 can represent the genes of highest variance
apple_var_rank %>%
ggplot(aes(x = var, y = rank)) +
geom_rect(
xmax = max(high_var_apple3000$var),
xmin = min(high_var_apple3000$var),
ymax = nrow(apple_var_rank),
ymin = nrow(apple_var_rank) - 3000,
fill = "dodgerblue2", alpha = 0.2
) +
geom_hline(
data = bait_var, aes(yintercept = rank),
color = "tomato1", size = 0.8, alpha = 0.5
) +
geom_vline(
data = bait_var, aes(xintercept = var),
color = "tomato1", size = 0.8, alpha = 0.5
) +
geom_line(size = 1.1) +
labs(y = "rank",
x = "variance",
caption = "Blue box = top 3000 high var genes.") +
theme_classic() +
theme(
text = element_text(size = 14),
axis.text = element_text(color = "black"),
plot.caption = element_text(hjust = 0)
)
Warning: Using `size` aesthetic for lines was deprecated in ggplot2 3.4.0.
ℹ Please use `linewidth` instead.
This warning is displayed once every 8 hours.
Call `lifecycle::last_lifecycle_warnings()` to see where this warning was generated.
ggsave("highvar3000.svg", height = 4, width = 5, bg ="white")
ggsave("highvar3000.png", height = 4, width = 5, bg ="white")

It looks like if we take the top 3000 genes, it takes pretty much the
entire upper elbow of the graph. And all the bait genes are just
included.
# Select high var gene
apple_exp_log_highvar = apple_exp_log_long %>%
filter(gene %in% high_var_apple3000$gene)
dim(apple_exp_log_highvar)
[1] 24000 4
filtered_genes <- apple_exp_log_highvar %>%
distinct(`Genbank number`)
write.table(filtered_genes, "high_var_apple.csv", row.names = FALSE, col.names = FALSE, quote = FALSE)
Gene-wise correlation
# pivot wider for as input for correlation
highvar_log_wide = apple_exp_log_highvar %>%
dplyr::select(gene,timepoint,log_exp) %>%
pivot_wider(names_from = timepoint, values_from = log_exp) %>%
as.data.frame()
row.names(highvar_log_wide) <- highvar_log_wide$gene
head(highvar_log_wide)
# correlate genes with each other
# As we not only focus on the trend, the value itself matters, we still use default setting for correlation (Pearson)
cor_matrix = cor(t(highvar_log_wide[, -1]))
dim(cor_matrix)
[1] 3000 3000
Edge selection
# In order to filter out the not meaning full relationships
# We use t distribution approximation, as for each correlation coeff (r), we can approximate a t statistics, under some arbitrary assumptions
n_daa = ncol(highvar_log_wide) -1
# deduplicate the lower part for the cor matrix
cor_matrix_upper_tri <- cor_matrix
cor_matrix_upper_tri[lower.tri(cor_matrix_upper_tri)] <- NA
# t = r*sqrt((n-2)/(1-r^2))
edge_table <- cor_matrix_upper_tri %>%
as.data.frame() %>%
mutate(from = row.names(cor_matrix)) %>%
pivot_longer(cols = !from, names_to = "to", values_to = "r") %>%
filter(is.na(r) == F) %>%
filter(from != to) %>%
mutate(t = r*sqrt((n_daa-2)/(1-r^2))) %>%
mutate(p.value = case_when(
t > 0 ~ pt(t, df = n_daa-2, lower.tail = F),
t <=0 ~ pt(t, df = n_daa-2, lower.tail = T)
)) %>%
mutate(FDR = p.adjust(p.value, method = "fdr"))
head(edge_table)
edge_table %>%
filter(r > 0) %>%
filter(FDR < 0.01) %>%
slice_min(order_by = abs(r), n = 10) # FDR cut off at 0.05, r > 0.9; cut off at 0.01, r > 0.97
# Visualize the distribution of r
edge_table %>%
ggplot(aes(x = r)) +
geom_histogram(color = "white", bins = 100) +
geom_vline(xintercept = 0.7, color = "tomato1", size = 1.2) +
theme_classic() +
theme(
text = element_text(size = 14),
axis.text = element_text(color = "black")
)
ggsave("edge_table_distribution.svg", height = 4, width = 5, bg ="white")
ggsave("edge_table_distribution.png", height = 4, width = 5, bg ="white")

The more strict the cutoff, the “fewer” relationship will be used to
contrust the co-expression network Looks like at r > 0.7 (red line),
the distribution trails off rapidly.
# bait gene co-expression
edge_table %>%
filter(str_detect(from,"EB107042") & str_detect(to, "CN943384") | str_detect(from,"CN943384") & str_detect(to, "EB107042") |
str_detect(from,"EB141951") & str_detect(to, "CN943384") | str_detect(from,"CN943384") & str_detect(to, "EB141951") |
str_detect(from,"EB107042") & str_detect(to, "EB141951") | str_detect(from,"EB141951") & str_detect(to, "EB107042"))
We can see that one of the bait gene seems not to be having that high
relationship with the other two. According to the paper, we can see the
trend from the 0 DAA to 14 DAA of EB107042 does vary from the others. So
as recommended by the workflow, it’s still good to use 0.7 as a cut
off.
edge_table_select = edge_table %>%
filter(r >= 0.07)
Reason for not using p value cut off alone?
Why do I warn against determining cutoffs using p values alone?
Because p value is a function of both effect size (r) and degrees of
freedom (df). Experiments with larger df produces smaller p values given
the same effect size. Let’s make a graph to illustrate that:
t_dist_example <- expand.grid(
df = c(2, 5, 10, 50, 80, 100),
r = c(0.2, 0.3, 0.5, 0.7, 0.8, 0.9, 0.99)
) %>%
mutate(t = r*sqrt((df-2)/(1-r^2))) %>%
mutate(p = pt(q = t, df = df, lower.tail = F))
t_dist_example %>%
ggplot(aes(x = r, y = -log10(p))) +
geom_line(aes(group = df, color = as.factor(df)),
size = 1.1, alpha = 0.8) +
geom_hline(yintercept = 2, color = "grey20", size = 1, linetype = 4) +
labs(color = "df",
caption = "dotted line: P = 0.01") +
theme_classic() +
theme(
legend.position = c(0.2, 0.6),
text = element_text(size = 14),
axis.text = element_text(color = "black"),
plot.caption = element_text(hjust = 0, size = 14)
)
Warning: A numeric `legend.position` argument in `theme()` was deprecated in ggplot2 3.5.0.
ℹ Please use the `legend.position.inside` argument of `theme()` instead.
This warning is displayed once every 8 hours.
Call `lifecycle::last_lifecycle_warnings()` to see where this warning was generated.
ggsave("t_dist_example.svg", height = 5, width = 6, bg ="white")
ggsave("t_dist_example.png", height = 5, width = 6, bg ="white")

As you can see, large size experiments (df = 80 or 100), you would
reach p < 0.01 with r value between 0.2 and 0.4. However, for
experiments with df at 5, you won’t get to p = 0.01 unless you have r
values closer to 0.9.
So it’s really related to df. In order to disregard the effect, we
can use bait genes to guid the cutoff. For now, I don’t have one.
Module Selection
Assign genes to different modules based on their correlation in
between, that is, to detect the co-expressed genes.
node_table = data.frame(
gene = c(edge_table_select$from, edge_table_select$to) %>% unique()
)
head(node_table)
dim(node_table)
[1] 3000 1
my_network = graph_from_data_frame(
edge_table_select,
vertices = node_table,
directed = F
)
How to determine the resolution for cluster leiden
optimize_resolution = function(network, resolution) {
modules = network %>%
cluster_leiden(resolution_parameter = resolution,
objective_function = "modularity")
parsed_modules = data.frame(
gene_ID = names(membership(modules)),
module = as.vector(membership(modules))
)
num_module_5 = parsed_modules %>%
group_by(module) %>%
count() %>%
arrange(-n) %>%
filter(n >= 5) %>%
nrow() %>%
as.numeric()
num_genes_contained = parsed_modules %>%
group_by(module) %>%
count() %>%
arrange(-n) %>%
filter(n >= 5) %>%
ungroup() %>%
summarise(sum = sum(n)) %>%
as.numeric()
c(num_module_5, num_genes_contained)
}
optimization_results_list = purrr::map(
.x = seq(from = 0.25, to = 5, by = 0.25),
.f = optimize_resolution,
network = my_network
)
Warning: The `resolution_parameter` argument of `cluster_leiden()` is deprecated as of igraph 2.1.0.
ℹ Please use the `resolution` argument instead.
This warning is displayed once every 8 hours.
Call `lifecycle::last_lifecycle_warnings()` to see where this warning was generated.
optimization_results_df = as.data.frame(do.call(cbind, optimization_results_list))
optimization_results = optimization_results_df %>%
t() %>%
cbind(resolution = seq(from = 0.25, to = 5, by = 0.25)) %>%
as.data.frame() %>%
rename(num_module = V1, num_contained_gene = V2)
Optimize_num_module = optimization_results %>%
ggplot(aes(x = resolution, y = num_module)) +
geom_line(size = 1.1, alpha = 0.8, color = "dodgerblue2") +
geom_point(size = 3, alpha = 0.7) +
geom_vline(xintercept = 2, size = 1, linetype = 4) + # resolution_parameter vareis, start trying with 1.5
labs(x = "resolution parameter", y = "num. modules\nw/ >=5 genes") +
theme_classic() + theme(text = element_text(size = 14), axis.text = element_text(color = "black"))
Optimize_num_gene = optimization_results %>%
ggplot(aes(x = resolution, y = num_contained_gene)) +
geom_line(size = 1.1, alpha = 0.8, color = "violetred2") +
geom_point(size = 3, alpha = 0.7) +
geom_vline(xintercept = 2, size = 1, linetype = 4) +
labs(x = "resolution parameter", y = "num. genes in\nmodules w/ >=5 genes") +
theme_classic() + theme(text = element_text(size = 14), axis.text = element_text(color = "black"))
Optimize_num_module / Optimize_num_gene
ggsave("resolution.svg", height = 5, width = 6, bg ="white")
ggsave("resolution.png", height = 5, width = 6, bg ="white")

set.seed(987)
modules = cluster_leiden(my_network, resolution_parameter = 2, objective_function = "modularity")
my_network_modules = data.frame(
gene = names(membership(modules)),
module = as.vector(membership(modules))
)
my_network_modules %>%
group_by(module) %>%
count() %>%
arrange(-n) %>%
filter(n >= 5)
12 modules contain more than 5 genes, covering 2560 genes.
my_network_modules
DGE analysis
apple_exp_log_wide = apple_exp_log_long %>%
dplyr::select(gene,timepoint,log_exp) %>%
pivot_wider(names_from = timepoint, values_from = log_exp) %>%
column_to_rownames("gene")
head(apple_exp_log_wide)
# manually calculate log2fc
# the reason for not using DESeq2 is they require raw count (integer) input
# the reason for not using limma is they require multiple replicates for each condition
log2_fc <- apple_exp_log_wide %>%
mutate(
log2FC_35_vs_60 = log2(`35 DAA` / `60 DAA`), # cell division end vs starch accumulation
log2FC_87_vs_132 = log2(`87 DAA` / `132 DAA`), # starch accumulation vs ripening
log2FC_35_vs_132 = log2(`35 DAA` / `132 DAA`), # cell division vs ripening
log2FC_60_vs_87 = log2(`60 DAA` / `87 DAA`) # starch accumulation vs end
)
head(log2_fc)
ripening_genes = log2_fc %>%
filter(log2FC_35_vs_132 < -1 & log2FC_87_vs_132 < -1 ) %>%
rownames() # In both comparison, significantly upregulated in ripening stages,可能与果实成熟相关。这些基因可能参与调控果实质地、风味、色泽的变化。
starch_reg_genes = log2_fc %>%
filter(abs(log2FC_60_vs_87) > 1) %>% # > 1: 60 > 87; < -1 60 < 87
rownames()
set.seed(987)
library(pheatmap)
heatmap_data_ripen <- apple_exp_log_wide[ripening_genes, ]
# Save as SVG
# svglite("heatmap_ripening.svg", width = 5, height = 6)
# heatmap
pheatmap(
heatmap_data_ripen,
cluster_rows = TRUE,
cluster_cols = FALSE,
show_rownames = TRUE,
scale = "row"
)

# dev.off()
set.seed(987)
library(pheatmap)
heatmap_data_starch <- apple_exp_log_wide[starch_reg_genes, ]
# svglite("heatmap_starch.svg", width = 5, height = 6)
# heatmap
pheatmap(
heatmap_data_starch,
cluster_rows = TRUE,
cluster_cols = FALSE,
show_rownames = FALSE,
scale = "row"
)

# dev.off()
# many genes
pheatmap(
apple_exp_log_wide,
cluster_rows = FALSE,
cluster_cols = FALSE,
show_rownames = FALSE,
scale = "row"
)

sample_info = data.frame(
sample = colnames(apple_exp_log_wide),
group = c("T0", "T14", "T25", "T35", "T60", "T87", "T132", "T146")
)
sample_info$group <- factor(sample_info$group, levels = unique(sample_info$group))
Enrichment caculation for certain genes
# Calculate enrichment for modules function
# Make sure you got the module_info_df.
# The format of module info df should be a 2 columns tibble with first column named gene, second column named module.
# The interesting_genes part should be a set of genes.
calculate_module_enrichment = function(module_info_df, interesting_genes) {
library(broom)
library(tidyverse)
df1 = module_info_df %>%
group_by(module) %>%
summarise(
raw_count = n(),
) %>%
ungroup()
df2 = data.frame(gene = interesting_genes) %>%
left_join(module_info_df, by = "gene") %>%
group_by(module) %>%
summarise(
raw_count = n(),
) %>%
ungroup()
all_modules = unique(df1$module)
result_df = tibble()
for (i in 1:length(all_modules)) {
module = all_modules[i]
raw_count_df1 = df1[df1$module == module, "raw_count"]
raw_count_df2 = na.omit(df2[df2$module == module, "raw_count"])
if (nrow(raw_count_df2) == 0) {
raw_count_df2 = 0
}
total_count_df1 = sum(df1$raw_count)
total_count_df2 = sum(df2$raw_count)
contingency_table = matrix(c(sum(raw_count_df2),
sum(raw_count_df1),
total_count_df2 - sum(raw_count_df2),
total_count_df1 - sum(raw_count_df1)),
nrow = 2)
fisher_test_result = tidy(fisher.test(contingency_table, alternative = "g"))
percentage = paste(round(sum(raw_count_df2) / total_count_df2 * 100, 2), "% / ",
round(sum(raw_count_df1) / total_count_df1 * 100, 2), "%", sep = "")
temp_df = tibble(module = module,
raw_count = paste(raw_count_df2, raw_count_df1, sep = "/"),
percentage = percentage)
temp_df = bind_cols(temp_df, fisher_test_result)
result_df = bind_rows(result_df, temp_df)
}
return(result_df)
}
Module trends
module_means <- apple_exp_log_highvar %>%
dplyr::select(gene, timepoint, log_exp) %>%
left_join(my_network_modules, by = join_by(gene)) %>%
filter(module %in% c(3, 2, 4, 7, 9)) %>%
mutate(timepoint = factor(timepoint, levels = c("0 DAA", "14 DAA", "25 DAA", "35 DAA", "60 DAA", "87 DAA", "132 DAA", "146 DAA"))) %>%
group_by(module,timepoint) %>%
summarise(mean_exp = mean(log_exp, na.rm = TRUE), .groups = "drop")
apple_exp_log_highvar %>%
dplyr::select(gene, timepoint, log_exp) %>%
left_join(my_network_modules, by = join_by(gene)) %>%
filter(module %in% c(3, 2, 4, 7, 9)) %>%
mutate(timepoint = factor(timepoint, levels = c("0 DAA", "14 DAA", "25 DAA", "35 DAA", "60 DAA", "87 DAA", "132 DAA", "146 DAA"))) %>%
ggplot(aes(x = timepoint, y = log_exp, group = gene)) +
# 大部分基因灰色线
geom_line(color = "grey", alpha = 0.5) +
# 添加模块均值粗线
geom_line(data = module_means, aes(x = timepoint, y = mean_exp, color = as.factor(module), group = module), size = 1.5) +
theme_minimal() +
labs(
title = "Expression Trends for Selected Modules",
x = "Timepoint (DAA)",
y = "Expression Value",
color = "Module"
) +
facet_wrap(~module, scales = "free_y") + # 分面展示每个模块
theme(axis.text.x = element_text(angle = 45, hjust = 1))
ggsave("expression_trends_top_modules.svg", height = 6, width =10, bg ="white")
ggsave("expression_trends_top_modules.png", height = 6, width =10, bg ="white")

calculate_module_enrichment(my_network_modules,ripening_genes)
apple_exp_log_highvar %>%
dplyr::select(gene, timepoint, log_exp) %>%
left_join(my_network_modules, by = join_by(gene)) %>%
filter(gene %in% ripening_genes) %>%
mutate(timepoint = factor(timepoint, levels = c("0 DAA", "14 DAA", "25 DAA", "35 DAA", "60 DAA", "87 DAA", "132 DAA", "146 DAA"))) %>%
ggplot(aes(x = timepoint, y = log_exp, group = gene, color = gene)) +
geom_point() +
geom_line(size = 0.5, alpha = 0.8) +
theme_minimal() +
labs(
title = "Expression Trends of Ripening Probes",
x = "Timepoint (DAA)",
y = "Log Expression Value",
color = "Gene"
) +
theme(axis.text.x = element_text(angle = 45, hjust = 1),legend.position = "none")
ggsave("expression_trends_ripening_log.svg", height = 5, width = 7, bg ="white")
ggsave("expression_trends_ripening_log.png", height = 5, width = 7, bg ="white")

apple_exp %>%
group_by(`Genbank number`) %>%
mutate(gene = case_when( # mutate a new col
n() > 1 ~ paste0(`Genbank number`, "_", row_number()), # "duplicates" will have more than 1 row. Add a symbol.
TRUE ~ `Genbank number` # keep the same for those not_duplicated
)) %>%
ungroup() %>%
rename_with(~ gsub(" Mean", "", .), contains("DAA")) %>% # Remove the "Mean" in colnames
pivot_longer(
cols = starts_with("0 DAA"):starts_with("146 DAA"),
names_to = "timepoint",
values_to = "expression"
) %>%
dplyr::select(gene, timepoint, expression) %>%
left_join(my_network_modules, by = join_by(gene)) %>%
filter(gene %in% ripening_genes) %>%
mutate(timepoint = factor(timepoint, levels = c("0 DAA", "14 DAA", "25 DAA", "35 DAA", "60 DAA", "87 DAA", "132 DAA", "146 DAA"))) %>%
ggplot(aes(x = timepoint, y = expression, group = gene, color = gene)) +
geom_point() +
geom_line(size = 0.5, alpha = 0.8) +
theme_minimal() +
labs(
title = "Expression Trends of Ripening Probes",
x = "Timepoint (DAA)",
y = "Expression Value",
color = "Gene"
) +
theme(axis.text.x = element_text(angle = 45, hjust = 1),legend.position = "none")
ggsave("expression_trends_ripening.svg", height = 5, width = 7, bg ="white")
ggsave("expression_trends_ripening.png", height = 5, width = 7, bg ="white")

calculate_module_enrichment(my_network_modules,bait_genes)
Seems like the CDKB2 and CKS1 homologues are assigned (clustered)
together based on the trend while CDKB1 is separated.
LS0tCnRpdGxlOiAiUiBOb3RlYm9vayIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKIyBBcHBsZSBkYXRhIHVzaW5nIFNpbXBsZVRpZHkgV29ya2Zsb3cKCkhlcmUgd2UgdXNlIGFub3RoZXIgZGF0YSBzZXQgdHJ5aW5nIHRvIGdvIHRocm91Z2ggdGhlIHNhbWUgd29ya2Zsb3cuCgojIFBhY2thZ2UgcmVxdWlyZW1lbnQKCmBgYHtyfQpsaWJyYXJ5KHN2Z2xpdGUpCmxpYnJhcnkobGltbWEpCmxpYnJhcnkodGlkeXZlcnNlKQpsaWJyYXJ5KGdncGxvdDIpCmxpYnJhcnkoaWdyYXBoKQpsaWJyYXJ5KGdncmFwaCkKCmxpYnJhcnkocmVhZHhsKQpsaWJyYXJ5KHBhdGNod29yaykKbGlicmFyeShSQ29sb3JCcmV3ZXIpCmxpYnJhcnkodmlyaWRpcykKYGBgCgojIERhdGEgcmVzb3VyY2UKCk1hbHVzIGRvbWVzdGljYS4gVGhlIGRhdGEgZnJhbWUgY29udGFpbnMgbWljcm9hcnJheSBnZW5lIGV4cHJlc3Npb24gZGF0YSwgd2l0aCBlYWNoIHJvdyBjb3JyZXNwb25kaW5nIHRvIGEgZ2VuZSBhbmQgZWFjaCBjb2x1bW4gcmVwcmVzZW50aW5nIGV4cHJlc3Npb24gbGV2ZWxzIGF0IGRpZmZlcmVudCB0aW1lIHBvaW50cy4gVGhlIGRhdGEgaW5jbHVkZXMgdGhlIG1lYW4gKE1lYW4pLCBudW1iZXIgb2YgZGF0YSBwb2ludHMgKG4pLCBhbmQgc3RhbmRhcmQgZXJyb3IgKFNFKS4gRm9yIHRoZSAwIERBQSB0aW1lIHBvaW50LCBvbmx5IG9uZSBiaW9sb2dpY2FsIHJlcGxpY2F0ZSB3YXMgc2FtcGxlZC4gRm9yIG90aGVyIHRpbWUgcG9pbnRzLCB0aGUgZGF0YSBpcyBiYXNlZCBvbiB0d28gYmlvbG9naWNhbCByZXBsaWNhdGVzIGFuZCB0d28gdGVjaG5pY2FsIHJlcGxpY2F0ZXMuIEluIHNvbWUgY2FzZXMsIGRhdGEgcG9pbnRzIHdlcmUgZXhjbHVkZWQgZm9yIHRlY2huaWNhbCByZWFzb25zLCBzbyB0aGUgbWVhbiBhbmQgc3RhbmRhcmQgZXJyb3IgYXJlIG9ubHkgY2FsY3VsYXRlZCBiYXNlZCBvbiB0aGUgcmVtYWluaW5nIGRhdGEuCgpgYGB7cn0KYXBwbGVfZXhwID0gcmVhZF9leGNlbCgiQXBwbGVfREFBX2V4cHJlc3Npb24ueGxzIixjb2xfdHlwZXMgPSAidGV4dCIpCmRpbShhcHBsZV9leHApCmBgYCAgIAoKVGhlcmUgYXJlIDE1OTg3IGdlbmVzIGFuZCA4IHRpbWUgcG9pbnQsIERBQSBzdGFuZHMgZm9yIERheXMgQWZ0ZXIgQW50aGVzaXMuCgpgYGB7cn0KIyBnZW5lcmFsIGNoZWNrIG9uIHRoZSBkZiBpbmZvCgphcHBsZV9leHAgJT4lCiAgbXV0YXRlKGFjcm9zcyhlbmRzX3dpdGgoIk1lYW4iKSwgYXMubnVtZXJpYykpICU+JQogIHNlbGVjdChlbmRzX3dpdGgoIk1lYW4iKSkgJT4lCiAgc3VtbWFyeSgpCmBgYAoKVXN1YWxseSwgd2hlbiB0aGUgbWlzc2luZyB2YWx1ZXMgd2VyZSBjYXVzZWQgYnkgdmVyeSBsb3cgZXhwcmVzc2lvbiB2YWx1ZXMgb3Igbm90IGRldGVjdGVkLCB0aGV5IHdlcmUgZ2VuZXJhbGx5IHJlcGxhY2VkIGJ5IDAuIApIb3dldmVyLCBhY2NvcmRpbmcgdG8gdGhlIGFydGljbGUsIHRoZSBOQSBwYXJ0IG9mIHRoaXMgZGF0YSBmcmFtZSB3YXMgZXhjbHVkZWQgZm9yIHRlY2huaWNhbCByZWFzb25zLCBzbyBoZXJlIHdlIGVtcGxveWVkIGFuIGV4Y2x1c2lvbiBzdHJhdGVneSwgaS5lLiwgd2UgZGlkIG5vdCBzZWxlY3QgdGhlIGdlbmVzIGNvbnRhaW5pbmcgdGhlIG1pc3NpbmcgdmFsdWVzIGZvciB0aGUgc3Vic2VxdWVudCBhbmFseXNpcy4KCgpgYGB7cn0KYXBwbGVfZXhwID0gYXBwbGVfZXhwICU+JQogIG11dGF0ZShhY3Jvc3MoZW5kc193aXRoKCJNZWFuIiksIGFzLm51bWVyaWMpKSAlPiUKICBzZWxlY3QoYEdlbmJhbmsgbnVtYmVyYCxlbmRzX3dpdGgoIk1lYW4iKSkgJT4lICMgU0UgYW5kIG4gaXMgZ29vZCB0byBrbm93LCBidXQgbm90IGZvciBzdWJzZXF1ZW50IGFuYWx5c2lzCiAgZHJvcF9uYSgpCgpkaW0oYXBwbGVfZXhwKSAjIDgzNiBnZW5lcyBleGNsdWRlZCwgNSUuIFZhbGlkYXRpb24gcmVxdWlyZWQgbGF0ZXIuCmBgYAoKYGBge3IsZXZhbD1GQUxTRX0Kd3JpdGUudGFibGUoYXBwbGVfZXhwICU+JQogIGRpc3RpbmN0KGBHZW5iYW5rIG51bWJlcmApLCAiZ2VuYmFua19hcHBsZS5jc3YiLCByb3cubmFtZXMgPSBGQUxTRSwgY29sLm5hbWVzID0gRkFMU0UsIHF1b3RlID0gRkFMU0UpCmBgYAoKCiMgUENBCgpgYGB7cn0KCmdncGxvdChhcHBsZV9leHAsIGFlcyh4ID0gYDAgREFBIE1lYW5gKSkgKwogIGdlb21faGlzdG9ncmFtKGJpbnMgPSAzMCkgCgpnZ3NhdmUoImhpc3RfYmVmb3JlLnN2ZyIsIGhlaWdodCA9IDMsIHdpZHRoID0gNCwgYmcgPSJ3aGl0ZSIpCmdnc2F2ZSgiaGlzdF9iZWZvcmUucG5nIiwgaGVpZ2h0ID0gMywgd2lkdGggPSA0LCBiZyA9IndoaXRlIikKIyBkZXYub2ZmKCkKYGBgCgoKYGBge3J9CmFwcGxlX2V4cF9sb2cgPSBhcHBsZV9leHAgJT4lCiAgbXV0YXRlKGFjcm9zcyhlbmRzX3dpdGgoIk1lYW4iKSwgfiBsb2cxMCguICsgMSkpKSAjIGxvZyB0cmFuc2Zvcm0gdGhlIGRmCgpnZ3Bsb3QoYXBwbGVfZXhwX2xvZywgYWVzKHggPSBgMCBEQUEgTWVhbmApKSArCiAgZ2VvbV9oaXN0b2dyYW0oYmlucyA9IDMwKSAjIFRoZSBkYXRhIGRpc3RyaWJ1dGlvbiBpcyBzdGlsbCBza2V3ZWQgZHVlIHRvIHRoZSBsb3cgZXhwcmVzc2lvbiB2YWx1ZXMgb2YgbW9zdCBnZW5lcywgYnV0IGl0IGlzIGJldHRlciB0aGFuIHVzaW5nIHJhdyB2YWx1ZXMKCmdnc2F2ZSgiaGlzdF9hZnRlci5zdmciLCBoZWlnaHQgPSAzLCB3aWR0aCA9IDQsIGJnID0id2hpdGUiKQpnZ3NhdmUoImhpc3RfYWZ0ZXIucG5nIiwgaGVpZ2h0ID0gMywgd2lkdGggPSA0LCBiZyA9IndoaXRlIikKYGBgCgoKYGBge3J9CmFwcGxlX3BjYSA9IHByY29tcCh0KGFwcGxlX2V4cF9sb2dbLCAtMV0pKQphcHBsZV9wY2FfaW1wID0gYXMuZGF0YS5mcmFtZSh0KHN1bW1hcnkoYXBwbGVfcGNhKSRpbXBvcnRhbmNlKSkKYXBwbGVfcGNhX2Nvb3JkID0gYXBwbGVfcGNhJHhbLCAxOjhdICU+JSAjIFRha2UgdGhlIGFsbCA4IFBDcwogIGFzLmRhdGEuZnJhbWUoKSAlPiUgCiAgbXV0YXRlKHRpbWVwb2ludCA9IHJvdy5uYW1lcyguKSkgIyByb3duYW1lcyB0byBjb2wKYXBwbGVfcGNhX2Nvb3JkCmBgYAoKQWNjb3JkaW5nIHRvIHRoZSBwYXBlciwgREFBIGFuZCB0aGUgZnJ1aXQgZGV2ZWxvcG1lbnQgc3RhZ2UgaGF2ZSBhIGNlcnRhaW4gY29ycmVzcG9uZGluZyByZWxhdGlvbnNoaXA6CgowLTM1IERBQSA6IENlbGwgZGl2aXNpb24KNjAtODcgREFBIDogU3RhcmNoIGFjY3VtdWxhdGlvbgoxMzItMTQ2IERBIDogUmlwZW5pbmcKCkFzIHdlIGRvbid0IGhhdmUgbWFueSBjb21iaW5hdGlvbiBvZiB0aGUgbGlicmFyaWVzLCB3ZSB3b24ndCBleHBlY3QgYSBzdHJvbmcgZXhwbGFuYXRpb24gYnkgc3RhZ2UgYnV0IGl0IHdvcnRoIGEgbG9vay4KCmBgYHtyfQojIG11dGF0ZSBhIHN0YWdlIGNvbHVtbgphcHBsZV9wY2FfY29vcmQgPSBhcHBsZV9wY2FfY29vcmQgJT4lCiAgbXV0YXRlKHN0YWdlID0gY2FzZV93aGVuKAogICAgdGltZXBvaW50ICVpbiUgYygiMCBEQUEgTWVhbiIsICIxNCBEQUEgTWVhbiIsICIyNSBEQUEgTWVhbiIsICIzNSBEQUEgTWVhbiIpIH4gIkNlbGwgZGl2aXNpb24iLAogICAgdGltZXBvaW50ICVpbiUgYygiNjAgREFBIE1lYW4iLCAiODcgREFBIE1lYW4iKSB+ICJTdGFyY2ggYWNjdW11bGF0aW9uIiwKICAgIHRpbWVwb2ludCAlaW4lIGMoIjEzMiBEQUEgTWVhbiIsICIxNDYgREFBIE1lYW4iKSB+ICJSaXBlbmluZyIsCiAgICBUUlVFIH4gIlVua25vd24iCiAgKSkgJT4lCiAgbXV0YXRlKHRpbWVwb2ludCA9IHN0cl9yZXBsYWNlKHRpbWVwb2ludCwgIiBNZWFuIiwgIiIpKSAjIFJlbW92ZSB0aGUgIk1lYW4iIHdvcmQgaW4gdGltZXBvaW50IGNvbAoKYXBwbGVfcGNhX2Nvb3JkICU+JSAKICBnZ3Bsb3QoYWVzKHggPSBQQzEsIHkgPSBQQzIpKSArCiAgZ2VvbV9wb2ludChhZXMoZmlsbCA9IHN0YWdlKSwgY29sb3IgPSAiZ3JleTIwIiwgc2hhcGUgPSAyMSwgc2l6ZSA9IDMsIGFscGhhID0gMC44KSArCiAgIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGMoIkNlbGwgZGl2aXNpb24iID0gIiMxYjllNzciLCAiU3RhcmNoIGFjY3VtdWxhdGlvbiIgPSAiI2Q5NWYwMiIsICJSaXBlbmluZyIgPSAiIzc1NzBiMyIpKSArCiAgbGFicyh4ID0gcGFzdGUoIlBDMSAoIiwgYXBwbGVfcGNhX2ltcFsxLCAyXSAlPiUgc2lnbmlmKDMpKjEwMCwgIiUgb2YgVmFyaWFuY2UpIiwgc2VwID0gIiIpLCAKICAgICAgIHkgPSBwYXN0ZSgiUEMyICgiLCBhcHBsZV9wY2FfaW1wWzIsIDJdICU+JSBzaWduaWYoMykqMTAwLCAiJSBvZiBWYXJpYW5jZSkiLCAiICAiLCBzZXAgPSAiIiksCiAgICAgICBmaWxsID0gInN0YWdlIikgKyAgCiAgdGhlbWVfYncoKSArCiAgdGhlbWUoCiAgICB0ZXh0ID0gZWxlbWVudF90ZXh0KHNpemU9IDE0KSwKICAgIGF4aXMudGV4dCA9IGVsZW1lbnRfdGV4dChjb2xvciA9ICJibGFjayIpCiAgKQoKZ2dzYXZlKCJwY2FfYXBwbGUuc3ZnIiwgaGVpZ2h0ID0gNCwgd2lkdGggPSA4LCBiZyA9IndoaXRlIikKZ2dzYXZlKCJwY2FfYXBwbGUucG5nIiwgaGVpZ2h0ID0gNCwgd2lkdGggPSA4LCBiZyA9IndoaXRlIikKYGBgCgpTaW5jZSBQQ0EgaXMgcGVyZm9ybWVkIGJhc2VkIG9uIGdlbmUgZXhwcmVzc2lvbiBkYXRhIGF0IGRpZmZlcmVudCB0aW1lIHBvaW50cywgdGhpcyBuYXR1cmFsbHkgY2F1c2VzIGRpZmZlcmVudCBkZXZlbG9wbWVudGFsIHN0YWdlcyB0byBzaG93IHNvbWUgc2VwYXJhdGlvbiBpbiBQQ0Egc3BhY2UuIApIb3dldmVyLCBQQ0EgaXRzZWxmIGlzIHVuc3VwZXJ2aXNlZCBhbmQgZG9lcyBub3Qga25vdyB0aGUgb3JkZXIgb2YgdGltZSBwb2ludHMsIHdoaWxlIHdlIHNlZSB0aGUgb3JkZXJpbmcgb2YgUEMxIGlzIGNvbnNpc3RlbnQgd2l0aCB0aGUgb3JkZXIgb2YgdGltZSBwb2ludHMsIGFuZCB0aGUgaW50ZXJ2YWwgYmV0d2VlbiBkaWZmZXJlbnQgc3RhZ2VzIGlzIG9idmlvdXMuIFRoaXMgc3VnZ2VzdHMgdGhhdCB0aGUgY2xvc2VyIHRoZSB0aW1lIGlzIHRvIGxhdGUgZGV2ZWxvcG1lbnQsIHRoZSBleHByZXNzaW9uIHBhdHRlcm4gd2lsbCBncmFkdWFsbHkgY2hhbmdlLCBhbmQgUENBIGhhcyBjYXB0dXJlZCB0aGlzIGZlYXR1cmUuCgojIEJhaXQgZ2VuZQoKQXMgdGhlIHJvd3MgaW4gdGhlIG1hdHJpeCBhcmUgbm90IGFjdHVhbCBnZW5lIG5hbWVzLCBhbmQgSSBkaWRuJ3QgbWFwIHRoZSBwcm9iZXMgdG8gdGhlIGdlbmVzLCBpdCdzIGhhcmQgdG8gZ2V0IHRoZSBiYWl0IGdlbmVzLiBIb3dldmVyLCB0aGUgcGFwZXIgcHJvdmlkZSB0aHJlZSBjb3JlIGNlbGwgY3ljbGUgZ2VuZXMsIHdoaWNoIGFyZToKRUIxMDcwNDI6IENES0IxOzIgaG9tb2xvZ3VlLApDTjk0MzM4NDogQ0RLQjI7MiBob21vbG9ndWUsCkVCMTQxOTUxOiBDS1MxIGhvbW9sb2d1ZQoKV2UnbGwgdXNlIHRoaXMgdGhyZWUgdG8gc2VydmUgYXMgYmFpdCBnZW5lCgpgYGB7cn0KYmFpdF9nZW5lcyA9IGMoIkVCMTA3MDQyIiwiQ045NDMzODQiLCJFQjE0MTk1MSIpCmBgYAoKCiMgRHVwbGljYXRlZCBnZW5lcwoKYGBge3J9CmFwcGxlX2V4cF9sb2cgJT4lCiAgZGltKCkKYGBgCgpgYGB7cn0KYXBwbGVfZXhwX2xvZyAlPiUKICBkaXN0aW5jdChgR2VuYmFuayBudW1iZXJgKSAlPiUKICBkaW0oKQpgYGAKCldlIG5vdGljZSB0aGF0IDE1MTUxID4gMTQ5MDIuIFRoZXJlIG11c3QgYmUgZHVwbGljYXRlZCBjb21iaW5hdGlvbjoKClRoZSByZWFzb24gZm9yIHRoaXMgImR1cGxpY2F0aW9uIiBpcyB0aGF0IHdlIGRlbGV0ZWQgdGhlIEVTVHMgKEV4cHJlc3NlZCBTZXF1ZW5jZSBUYWdzKSBjb2x1bW4gYXQgdGhlIGJlZ2lubmluZyBvZiB0aGUgZGF0YSBwcm9jZXNzaW5nIGFuZCB1c2VkIHRoZSBHZW5iYW5rIG51bWJlciBhcyB0aGUgZ2VuZSBpZGVudGlmaWNhdGlvbi4KClRoaXMgbWF5IHJlcHJlc2VudCBkaWZmZXJlbnQgZnJhZ21lbnRzIG9yIHRyYW5zY3JpcHRzIG9mIHRoZSBzYW1lIGdlbmUuIFdlIHNob3VsZCBhbHRlciBiYXNlZCBvbiB0aGUgZ2VuZSBpZGVudGlmaWNhdGlvbiB0byBkaWZmZXJlbnRpYXRlIHRoZW0uCgpgYGB7cn0KYXBwbGVfZXhwX2xvZyA9IGFwcGxlX2V4cF9sb2cgJT4lCiAgZ3JvdXBfYnkoYEdlbmJhbmsgbnVtYmVyYCkgJT4lCiAgbXV0YXRlKGdlbmUgPSBjYXNlX3doZW4oICMgbXV0YXRlIGEgbmV3IGNvbCAKICAgIG4oKSA+IDEgfiBwYXN0ZTAoYEdlbmJhbmsgbnVtYmVyYCwgIl8iLCByb3dfbnVtYmVyKCkpLCAjICJkdXBsaWNhdGVzIiB3aWxsIGhhdmUgbW9yZSB0aGFuIDEgcm93LiBBZGQgYSBzeW1ib2wuCiAgICBUUlVFIH4gYEdlbmJhbmsgbnVtYmVyYCAjIGtlZXAgdGhlIHNhbWUgZm9yIHRob3NlIG5vdF9kdXBsaWNhdGVkCiAgKSkgJT4lCiAgdW5ncm91cCgpICMgJT4lCiAgIyBmaWx0ZXIoZ2VuZSAhPSBgR2VuYmFuayBudW1iZXJgKSAjIGhvdyB0byBjaGVjayBmb3IgdGhlIGFsdGVyZWQgcm93cwpgYGAKCgojIEdlbmUgc2VsZWN0aW9uCgpgYGB7cn0KIyBsb25nIHZlcnNpb24KYXBwbGVfZXhwX2xvZ19sb25nID0gYXBwbGVfZXhwX2xvZyAlPiUKICByZW5hbWVfd2l0aCh+IGdzdWIoIiBNZWFuIiwgIiIsIC4pLCBjb250YWlucygiREFBIikpICU+JSAjIFJlbW92ZSB0aGUgIk1lYW4iIGluIGNvbG5hbWVzCiAgcGl2b3RfbG9uZ2VyKAogICAgY29scyA9IHN0YXJ0c193aXRoKCIwIERBQSIpOnN0YXJ0c193aXRoKCIxNDYgREFBIiksIAogICAgbmFtZXNfdG8gPSAidGltZXBvaW50IiwgCiAgICB2YWx1ZXNfdG8gPSAibG9nX2V4cCIKICApIApgYGAKCkFzIHdlIGtub3csIHRoZSBudW1iZXIgb2YgY29ycmVsYXRpb25zIHNjYWxlcyB0byB0aGUgc3F1YXJlIG9mIG51bWJlciBvZiBnZW5lcy4KSW4gb3JkZXIgdG8gY2FsY3VsYXRlIGdlbmUgY29ycmVsYXRpb24gYmV0d2VlbiBlYWNoIG90aGVyLCAxNTAwMCsgZ2VuZXMgYXJlIHRvbyBtdWNoLiAKV2UgY2FuIHNlbGVjdCBvbmx5IHRoZSBoaWdoIHZhcmlhbmNlIGdlbmVzLCBhcyBhIGdlbmUgaXMgdW5saWtlbHkgdG8gYmUgaW52b2x2ZWQgaW4gYSBwYXJ0aWN1bGFyIHN0YWdlIGlmIGl0J3MgZXhwcmVzc2VkIGF0IGEgc2ltaWxhciBsZXZlbCBhY3Jvc3MgYWxsIHRpbWVwb2ludHMuCgoKYGBge3J9CiMgQ2FsY3VsYXRlIHRoZSByYW5rCmFwcGxlX3Zhcl9yYW5rID0gYXBwbGVfZXhwX2xvZ19sb25nICU+JSAKICBncm91cF9ieShnZW5lKSAlPiUgCiAgc3VtbWFyaXNlKHZhciA9IHZhcihsb2dfZXhwKSkgJT4lICMgY2FsY3VsYXRlIHRoZSB2YXJpYW5jZSBmb3IgZWFjaCBnZW5lCiAgdW5ncm91cCgpICU+JSAKICBtdXRhdGUocmFuayA9IHJhbmsodmFyLCB0aWVzLm1ldGhvZCA9ICJhdmVyYWdlIikpICMgcmFuayB0aGUgZ2VuZXMKCmhlYWQoYXBwbGVfdmFyX3JhbmspCmBgYAoKYGBge3J9CiMgSWYgd2UgdGFrZSB0aGUgdG9wIDEvMyBvZiB0aGUgaGlnaGVzdCB2YXJpYW5jZSBnZW5lcwpoaWdoX3Zhcl9hcHBsZSA9IGFwcGxlX2V4cF9sb2dfbG9uZyAlPiUgCiAgZ3JvdXBfYnkoZ2VuZSkgJT4lIAogIHN1bW1hcmlzZSh2YXIgPSB2YXIobG9nX2V4cCkpICU+JSAKICB1bmdyb3VwKCkgJT4lIAogIGZpbHRlcih2YXIgPiBxdWFudGlsZSh2YXIsIDAuNjY3KSkgCgojIEFuZCB0YWtlIHRoZSB0b3AgMzAwMApoaWdoX3Zhcl9hcHBsZTMwMDAgPSBoaWdoX3Zhcl9hcHBsZSAlPiUgCiAgc2xpY2VfbWF4KG9yZGVyX2J5ID0gdmFyLCBuID0gMzAwMCkgCmBgYAoKYGBge3J9CmJhaXRfdmFyID0gYXBwbGVfdmFyX3JhbmsgJT4lCiAgZmlsdGVyKGdlbmUgJWluJSBiYWl0X2dlbmVzKSAKYGBgCgoKYGBge3J9CiMgQ2hlY2sgd2hldGhlciB0b3AgMzAwMCBjYW4gcmVwcmVzZW50IHRoZSBnZW5lcyBvZiBoaWdoZXN0IHZhcmlhbmNlCmFwcGxlX3Zhcl9yYW5rICU+JSAKICBnZ3Bsb3QoYWVzKHggPSB2YXIsIHkgPSByYW5rKSkgKwogICBnZW9tX3JlY3QoIAogICAgeG1heCA9IG1heChoaWdoX3Zhcl9hcHBsZTMwMDAkdmFyKSwgCiAgICB4bWluID0gbWluKGhpZ2hfdmFyX2FwcGxlMzAwMCR2YXIpLAogICAgeW1heCA9IG5yb3coYXBwbGVfdmFyX3JhbmspLAogICAgeW1pbiA9IG5yb3coYXBwbGVfdmFyX3JhbmspIC0gMzAwMCwKICAgIGZpbGwgPSAiZG9kZ2VyYmx1ZTIiLCBhbHBoYSA9IDAuMgogICAgKSArCiAgZ2VvbV9obGluZSgKICAgIGRhdGEgPSBiYWl0X3ZhciwgYWVzKHlpbnRlcmNlcHQgPSByYW5rKSwKICAgIGNvbG9yID0gInRvbWF0bzEiLCBzaXplID0gMC44LCBhbHBoYSA9IDAuNQogICkgKwogIGdlb21fdmxpbmUoCiAgICBkYXRhID0gYmFpdF92YXIsIGFlcyh4aW50ZXJjZXB0ID0gdmFyKSwgCiAgICBjb2xvciA9ICJ0b21hdG8xIiwgc2l6ZSA9IDAuOCwgYWxwaGEgPSAwLjUKICApICsgCiAgZ2VvbV9saW5lKHNpemUgPSAxLjEpICsKICBsYWJzKHkgPSAicmFuayIsCiAgICAgICB4ID0gInZhcmlhbmNlIiwKICAgICAgIGNhcHRpb24gPSAiQmx1ZSBib3ggPSB0b3AgMzAwMCBoaWdoIHZhciBnZW5lcy4iKSArCiAgdGhlbWVfY2xhc3NpYygpICsKICB0aGVtZSgKICAgIHRleHQgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDE0KSwKICAgIGF4aXMudGV4dCA9IGVsZW1lbnRfdGV4dChjb2xvciA9ICJibGFjayIpLAogICAgcGxvdC5jYXB0aW9uID0gZWxlbWVudF90ZXh0KGhqdXN0ID0gMCkKICApCgpnZ3NhdmUoImhpZ2h2YXIzMDAwLnN2ZyIsIGhlaWdodCA9IDQsIHdpZHRoID0gNSwgYmcgPSJ3aGl0ZSIpCmdnc2F2ZSgiaGlnaHZhcjMwMDAucG5nIiwgaGVpZ2h0ID0gNCwgd2lkdGggPSA1LCBiZyA9IndoaXRlIikKYGBgCgpJdCBsb29rcyBsaWtlIGlmIHdlIHRha2UgdGhlIHRvcCAzMDAwIGdlbmVzLCBpdCB0YWtlcyBwcmV0dHkgbXVjaCB0aGUgZW50aXJlIHVwcGVyIGVsYm93IG9mIHRoZSBncmFwaC4gQW5kIGFsbCB0aGUgYmFpdCBnZW5lcyBhcmUganVzdCBpbmNsdWRlZC4KCgpgYGB7cn0KIyBTZWxlY3QgaGlnaCB2YXIgZ2VuZQphcHBsZV9leHBfbG9nX2hpZ2h2YXIgPSBhcHBsZV9leHBfbG9nX2xvbmcgJT4lIAogIGZpbHRlcihnZW5lICVpbiUgaGlnaF92YXJfYXBwbGUzMDAwJGdlbmUpCmRpbShhcHBsZV9leHBfbG9nX2hpZ2h2YXIpCmBgYAoKCmBgYHtyLGV2YWw9RkFMU0V9CmZpbHRlcmVkX2dlbmVzIDwtIGFwcGxlX2V4cF9sb2dfaGlnaHZhciAlPiUKICBkaXN0aW5jdChgR2VuYmFuayBudW1iZXJgKQoKd3JpdGUudGFibGUoZmlsdGVyZWRfZ2VuZXMsICJoaWdoX3Zhcl9hcHBsZS5jc3YiLCByb3cubmFtZXMgPSBGQUxTRSwgY29sLm5hbWVzID0gRkFMU0UsIHF1b3RlID0gRkFMU0UpCmBgYAoKCiMgR2VuZS13aXNlIGNvcnJlbGF0aW9uCgpgYGB7cn0KIyBwaXZvdCB3aWRlciBmb3IgYXMgaW5wdXQgZm9yIGNvcnJlbGF0aW9uCmhpZ2h2YXJfbG9nX3dpZGUgPSBhcHBsZV9leHBfbG9nX2hpZ2h2YXIgJT4lCiAgZHBseXI6OnNlbGVjdChnZW5lLHRpbWVwb2ludCxsb2dfZXhwKSAlPiUKICBwaXZvdF93aWRlcihuYW1lc19mcm9tID0gdGltZXBvaW50LCB2YWx1ZXNfZnJvbSA9IGxvZ19leHApICU+JQogIGFzLmRhdGEuZnJhbWUoKQoKcm93Lm5hbWVzKGhpZ2h2YXJfbG9nX3dpZGUpIDwtIGhpZ2h2YXJfbG9nX3dpZGUkZ2VuZQpoZWFkKGhpZ2h2YXJfbG9nX3dpZGUpCmBgYAoKYGBge3J9CiMgY29ycmVsYXRlIGdlbmVzIHdpdGggZWFjaCBvdGhlcgojIEFzIHdlIG5vdCBvbmx5IGZvY3VzIG9uIHRoZSB0cmVuZCwgdGhlIHZhbHVlIGl0c2VsZiBtYXR0ZXJzLCB3ZSBzdGlsbCB1c2UgZGVmYXVsdCBzZXR0aW5nIGZvciBjb3JyZWxhdGlvbiAoUGVhcnNvbikKY29yX21hdHJpeCA9IGNvcih0KGhpZ2h2YXJfbG9nX3dpZGVbLCAtMV0pKQpkaW0oY29yX21hdHJpeCkKYGBgCgojIEVkZ2Ugc2VsZWN0aW9uIAoKYGBge3J9CiMgSW4gb3JkZXIgdG8gZmlsdGVyIG91dCB0aGUgbm90IG1lYW5pbmcgZnVsbCByZWxhdGlvbnNoaXBzCiMgV2UgdXNlIHQgZGlzdHJpYnV0aW9uIGFwcHJveGltYXRpb24sIGFzIGZvciBlYWNoIGNvcnJlbGF0aW9uIGNvZWZmIChyKSwgd2UgY2FuIGFwcHJveGltYXRlIGEgdCBzdGF0aXN0aWNzLCB1bmRlciBzb21lIGFyYml0cmFyeSBhc3N1bXB0aW9ucwpuX2RhYSA9IG5jb2woaGlnaHZhcl9sb2dfd2lkZSkgLTEKCiMgZGVkdXBsaWNhdGUgdGhlIGxvd2VyIHBhcnQgZm9yIHRoZSBjb3IgbWF0cml4CmNvcl9tYXRyaXhfdXBwZXJfdHJpIDwtIGNvcl9tYXRyaXgKY29yX21hdHJpeF91cHBlcl90cmlbbG93ZXIudHJpKGNvcl9tYXRyaXhfdXBwZXJfdHJpKV0gPC0gTkEKCiMgdCA9IHIqc3FydCgobi0yKS8oMS1yXjIpKQplZGdlX3RhYmxlIDwtIGNvcl9tYXRyaXhfdXBwZXJfdHJpICU+JSAKICBhcy5kYXRhLmZyYW1lKCkgJT4lIAogIG11dGF0ZShmcm9tID0gcm93Lm5hbWVzKGNvcl9tYXRyaXgpKSAlPiUgCiAgcGl2b3RfbG9uZ2VyKGNvbHMgPSAhZnJvbSwgbmFtZXNfdG8gPSAidG8iLCB2YWx1ZXNfdG8gPSAiciIpICU+JSAKICBmaWx0ZXIoaXMubmEocikgPT0gRikgJT4lIAogIGZpbHRlcihmcm9tICE9IHRvKSAlPiUgCiAgbXV0YXRlKHQgPSByKnNxcnQoKG5fZGFhLTIpLygxLXJeMikpKSAlPiUgCiAgbXV0YXRlKHAudmFsdWUgPSBjYXNlX3doZW4oCiAgICB0ID4gMCB+IHB0KHQsIGRmID0gbl9kYWEtMiwgbG93ZXIudGFpbCA9IEYpLAogICAgdCA8PTAgfiBwdCh0LCBkZiA9IG5fZGFhLTIsIGxvd2VyLnRhaWwgPSBUKQogICkpICU+JSAKICBtdXRhdGUoRkRSID0gcC5hZGp1c3QocC52YWx1ZSwgbWV0aG9kID0gImZkciIpKSAKCmhlYWQoZWRnZV90YWJsZSkKYGBgCgoKYGBge3J9CmVkZ2VfdGFibGUgJT4lIAogIGZpbHRlcihyID4gMCkgJT4lIAogIGZpbHRlcihGRFIgPCAwLjAxKSAlPiUgCiAgc2xpY2VfbWluKG9yZGVyX2J5ID0gYWJzKHIpLCBuID0gMTApICMgRkRSIGN1dCBvZmYgYXQgMC4wNSwgciA+IDAuOTsgY3V0IG9mZiBhdCAwLjAxLCByID4gMC45NwpgYGAKCgpgYGB7cn0KIyBWaXN1YWxpemUgdGhlIGRpc3RyaWJ1dGlvbiBvZiByCmVkZ2VfdGFibGUgJT4lIAogIGdncGxvdChhZXMoeCA9IHIpKSArCiAgZ2VvbV9oaXN0b2dyYW0oY29sb3IgPSAid2hpdGUiLCBiaW5zID0gMTAwKSArCiAgZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gMC43LCBjb2xvciA9ICJ0b21hdG8xIiwgc2l6ZSA9IDEuMikgKwogIHRoZW1lX2NsYXNzaWMoKSArCiAgdGhlbWUoCiAgICB0ZXh0ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxNCksCiAgICBheGlzLnRleHQgPSBlbGVtZW50X3RleHQoY29sb3IgPSAiYmxhY2siKQogICkKCmdnc2F2ZSgiZWRnZV90YWJsZV9kaXN0cmlidXRpb24uc3ZnIiwgaGVpZ2h0ID0gNCwgd2lkdGggPSA1LCBiZyA9IndoaXRlIikKZ2dzYXZlKCJlZGdlX3RhYmxlX2Rpc3RyaWJ1dGlvbi5wbmciLCBoZWlnaHQgPSA0LCB3aWR0aCA9IDUsIGJnID0id2hpdGUiKQpgYGAKClRoZSBtb3JlIHN0cmljdCB0aGUgY3V0b2ZmLCB0aGUgImZld2VyIiByZWxhdGlvbnNoaXAgd2lsbCBiZSB1c2VkIHRvIGNvbnRydXN0IHRoZSBjby1leHByZXNzaW9uIG5ldHdvcmsKTG9va3MgbGlrZSBhdCByID4gMC43IChyZWQgbGluZSksIHRoZSBkaXN0cmlidXRpb24gdHJhaWxzIG9mZiByYXBpZGx5LgoKYGBge3J9CiMgYmFpdCBnZW5lIGNvLWV4cHJlc3Npb24KZWRnZV90YWJsZSAlPiUKICBmaWx0ZXIoc3RyX2RldGVjdChmcm9tLCJFQjEwNzA0MiIpICYgc3RyX2RldGVjdCh0bywgIkNOOTQzMzg0IikgfCBzdHJfZGV0ZWN0KGZyb20sIkNOOTQzMzg0IikgJiBzdHJfZGV0ZWN0KHRvLCAiRUIxMDcwNDIiKSB8IAogICAgICAgICAgIHN0cl9kZXRlY3QoZnJvbSwiRUIxNDE5NTEiKSAmIHN0cl9kZXRlY3QodG8sICJDTjk0MzM4NCIpIHwgc3RyX2RldGVjdChmcm9tLCJDTjk0MzM4NCIpICYgc3RyX2RldGVjdCh0bywgIkVCMTQxOTUxIikgfCAKICAgICAgICAgICBzdHJfZGV0ZWN0KGZyb20sIkVCMTA3MDQyIikgJiBzdHJfZGV0ZWN0KHRvLCAiRUIxNDE5NTEiKSB8IHN0cl9kZXRlY3QoZnJvbSwiRUIxNDE5NTEiKSAmIHN0cl9kZXRlY3QodG8sICJFQjEwNzA0MiIpKQpgYGAKCldlIGNhbiBzZWUgdGhhdCBvbmUgb2YgdGhlIGJhaXQgZ2VuZSBzZWVtcyBub3QgdG8gYmUgaGF2aW5nIHRoYXQgaGlnaCByZWxhdGlvbnNoaXAgd2l0aCB0aGUgb3RoZXIgdHdvLiBBY2NvcmRpbmcgdG8gdGhlIHBhcGVyLCB3ZSBjYW4gc2VlIHRoZSB0cmVuZCBmcm9tIHRoZSAwIERBQSB0byAxNCBEQUEgb2YgRUIxMDcwNDIgZG9lcyB2YXJ5IGZyb20gdGhlIG90aGVycy4gU28gYXMgcmVjb21tZW5kZWQgYnkgdGhlIHdvcmtmbG93LCBpdCdzIHN0aWxsIGdvb2QgdG8gdXNlIDAuNyBhcyBhIGN1dCBvZmYuCgpgYGB7cn0KZWRnZV90YWJsZV9zZWxlY3QgPSBlZGdlX3RhYmxlICU+JQogICAgICBmaWx0ZXIociA+PSAwLjA3KQpgYGAKCgojIyBSZWFzb24gZm9yIG5vdCB1c2luZyBwIHZhbHVlIGN1dCBvZmYgYWxvbmU/CldoeSBkbyBJIHdhcm4gYWdhaW5zdCBkZXRlcm1pbmluZyBjdXRvZmZzIHVzaW5nIHAgdmFsdWVzIGFsb25lPyAKQmVjYXVzZSBwIHZhbHVlIGlzIGEgZnVuY3Rpb24gb2YgYm90aCBlZmZlY3Qgc2l6ZSAocikgYW5kIGRlZ3JlZXMgb2YgZnJlZWRvbSAoZGYpLiAKRXhwZXJpbWVudHMgd2l0aCBsYXJnZXIgZGYgcHJvZHVjZXMgc21hbGxlciBwIHZhbHVlcyBnaXZlbiB0aGUgc2FtZSBlZmZlY3Qgc2l6ZS4gCkxldCdzIG1ha2UgYSBncmFwaCB0byBpbGx1c3RyYXRlIHRoYXQ6CgpgYGB7cn0KdF9kaXN0X2V4YW1wbGUgPC0gZXhwYW5kLmdyaWQoCiAgZGYgPSBjKDIsIDUsIDEwLCA1MCwgODAsIDEwMCksCiAgciA9IGMoMC4yLCAwLjMsIDAuNSwgMC43LCAwLjgsIDAuOSwgMC45OSkKICApICU+JSAKICBtdXRhdGUodCA9IHIqc3FydCgoZGYtMikvKDEtcl4yKSkpICU+JSAKICBtdXRhdGUocCA9IHB0KHEgPSB0LCBkZiA9IGRmLCBsb3dlci50YWlsID0gRikpCiAgCnRfZGlzdF9leGFtcGxlICU+JSAKICBnZ3Bsb3QoYWVzKHggPSByLCB5ID0gLWxvZzEwKHApKSkgKwogIGdlb21fbGluZShhZXMoZ3JvdXAgPSBkZiwgY29sb3IgPSBhcy5mYWN0b3IoZGYpKSwgCiAgICAgICAgICAgIHNpemUgPSAxLjEsIGFscGhhID0gMC44KSArCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMiwgY29sb3IgPSAiZ3JleTIwIiwgc2l6ZSA9IDEsIGxpbmV0eXBlID0gNCkgKwogIGxhYnMoY29sb3IgPSAiZGYiLAogICAgICAgY2FwdGlvbiA9ICJkb3R0ZWQgbGluZTogUCA9IDAuMDEiKSArCiAgdGhlbWVfY2xhc3NpYygpICsKICB0aGVtZSgKICAgIGxlZ2VuZC5wb3NpdGlvbiA9IGMoMC4yLCAwLjYpLAogICAgdGV4dCA9IGVsZW1lbnRfdGV4dChzaXplID0gMTQpLAogICAgYXhpcy50ZXh0ID0gZWxlbWVudF90ZXh0KGNvbG9yID0gImJsYWNrIiksCiAgICBwbG90LmNhcHRpb24gPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLCBzaXplID0gMTQpCiAgKQoKZ2dzYXZlKCJ0X2Rpc3RfZXhhbXBsZS5zdmciLCBoZWlnaHQgPSA1LCB3aWR0aCA9IDYsIGJnID0id2hpdGUiKQpnZ3NhdmUoInRfZGlzdF9leGFtcGxlLnBuZyIsIGhlaWdodCA9IDUsIHdpZHRoID0gNiwgYmcgPSJ3aGl0ZSIpCmBgYApBcyB5b3UgY2FuIHNlZSwgbGFyZ2Ugc2l6ZSBleHBlcmltZW50cyAoZGYgPSA4MCBvciAxMDApLCB5b3Ugd291bGQgcmVhY2ggcCA8IDAuMDEgd2l0aCByIHZhbHVlIGJldHdlZW4gMC4yIGFuZCAwLjQuCkhvd2V2ZXIsIGZvciBleHBlcmltZW50cyB3aXRoIGRmIGF0IDUsIHlvdSB3b24ndCBnZXQgdG8gcCA9IDAuMDEgdW5sZXNzIHlvdSBoYXZlIHIgdmFsdWVzIGNsb3NlciB0byAwLjkuIAoKPiBTbyBpdCdzIHJlYWxseSByZWxhdGVkIHRvIGRmLiBJbiBvcmRlciB0byBkaXNyZWdhcmQgdGhlIGVmZmVjdCwgd2UgY2FuIHVzZSBiYWl0IGdlbmVzIHRvIGd1aWQgdGhlIGN1dG9mZi4gRm9yIG5vdywgSSBkb24ndCBoYXZlIG9uZS4KCiMgTW9kdWxlIFNlbGVjdGlvbgoKQXNzaWduIGdlbmVzIHRvIGRpZmZlcmVudCBtb2R1bGVzIGJhc2VkIG9uIHRoZWlyIGNvcnJlbGF0aW9uIGluIGJldHdlZW4sIHRoYXQgaXMsIHRvIGRldGVjdCB0aGUgY28tZXhwcmVzc2VkIGdlbmVzLgoKYGBge3J9Cm5vZGVfdGFibGUgPSBkYXRhLmZyYW1lKAogICAgZ2VuZSA9IGMoZWRnZV90YWJsZV9zZWxlY3QkZnJvbSwgZWRnZV90YWJsZV9zZWxlY3QkdG8pICU+JSB1bmlxdWUoKQogICkKCmhlYWQobm9kZV90YWJsZSkKZGltKG5vZGVfdGFibGUpCmBgYAoKCmBgYHtyfQpteV9uZXR3b3JrID0gZ3JhcGhfZnJvbV9kYXRhX2ZyYW1lKAogICAgZWRnZV90YWJsZV9zZWxlY3QsCiAgICB2ZXJ0aWNlcyA9IG5vZGVfdGFibGUsCiAgICBkaXJlY3RlZCA9IEYKICApCmBgYAoKCiMjIEhvdyB0byBkZXRlcm1pbmUgdGhlIHJlc29sdXRpb24gZm9yIGNsdXN0ZXIgbGVpZGVuCmBgYHtyfQpvcHRpbWl6ZV9yZXNvbHV0aW9uID0gZnVuY3Rpb24obmV0d29yaywgcmVzb2x1dGlvbikgewogICAgbW9kdWxlcyA9IG5ldHdvcmsgJT4lIAogICAgICBjbHVzdGVyX2xlaWRlbihyZXNvbHV0aW9uX3BhcmFtZXRlciA9IHJlc29sdXRpb24sIAogICAgICAgICAgICAgICAgICAgICBvYmplY3RpdmVfZnVuY3Rpb24gPSAibW9kdWxhcml0eSIpCiAgCiAgICBwYXJzZWRfbW9kdWxlcyA9IGRhdGEuZnJhbWUoCiAgICAgIGdlbmVfSUQgPSBuYW1lcyhtZW1iZXJzaGlwKG1vZHVsZXMpKSwKICAgICAgbW9kdWxlID0gYXMudmVjdG9yKG1lbWJlcnNoaXAobW9kdWxlcykpCiAgICApCgogICAgbnVtX21vZHVsZV81ID0gcGFyc2VkX21vZHVsZXMgJT4lCiAgICAgIGdyb3VwX2J5KG1vZHVsZSkgJT4lCiAgICAgIGNvdW50KCkgJT4lCiAgICAgIGFycmFuZ2UoLW4pICU+JQogICAgICBmaWx0ZXIobiA+PSA1KSAlPiUKICAgICAgbnJvdygpICU+JQogICAgICBhcy5udW1lcmljKCkKCiAgICBudW1fZ2VuZXNfY29udGFpbmVkID0gcGFyc2VkX21vZHVsZXMgJT4lCiAgICAgIGdyb3VwX2J5KG1vZHVsZSkgJT4lCiAgICAgIGNvdW50KCkgJT4lCiAgICAgIGFycmFuZ2UoLW4pICU+JQogICAgICBmaWx0ZXIobiA+PSA1KSAlPiUKICAgICAgdW5ncm91cCgpICU+JQogICAgICBzdW1tYXJpc2Uoc3VtID0gc3VtKG4pKSAlPiUKICAgICAgYXMubnVtZXJpYygpCgogICAgYyhudW1fbW9kdWxlXzUsIG51bV9nZW5lc19jb250YWluZWQpCn0KYGBgCgoKYGBge3J9Cm9wdGltaXphdGlvbl9yZXN1bHRzX2xpc3QgPSBwdXJycjo6bWFwKAogICAgLnggPSBzZXEoZnJvbSA9IDAuMjUsIHRvID0gNSwgYnkgPSAwLjI1KSwKICAgIC5mID0gb3B0aW1pemVfcmVzb2x1dGlvbiwgCiAgICBuZXR3b3JrID0gbXlfbmV0d29yawogICAgKQoKb3B0aW1pemF0aW9uX3Jlc3VsdHNfZGYgPSBhcy5kYXRhLmZyYW1lKGRvLmNhbGwoY2JpbmQsIG9wdGltaXphdGlvbl9yZXN1bHRzX2xpc3QpKQogIApvcHRpbWl6YXRpb25fcmVzdWx0cyA9IG9wdGltaXphdGlvbl9yZXN1bHRzX2RmICU+JQogICAgdCgpICU+JSAKICAgIGNiaW5kKHJlc29sdXRpb24gPSBzZXEoZnJvbSA9IDAuMjUsIHRvID0gNSwgYnkgPSAwLjI1KSkgJT4lIAogICAgYXMuZGF0YS5mcmFtZSgpICU+JSAKICAgIHJlbmFtZShudW1fbW9kdWxlID0gVjEsIG51bV9jb250YWluZWRfZ2VuZSA9IFYyKQoKT3B0aW1pemVfbnVtX21vZHVsZSA9IG9wdGltaXphdGlvbl9yZXN1bHRzICU+JQogICAgZ2dwbG90KGFlcyh4ID0gcmVzb2x1dGlvbiwgeSA9IG51bV9tb2R1bGUpKSArCiAgICBnZW9tX2xpbmUoc2l6ZSA9IDEuMSwgYWxwaGEgPSAwLjgsIGNvbG9yID0gImRvZGdlcmJsdWUyIikgKwogICAgZ2VvbV9wb2ludChzaXplID0gMywgYWxwaGEgPSAwLjcpICsKICAgIGdlb21fdmxpbmUoeGludGVyY2VwdCA9IDIsIHNpemUgPSAxLCBsaW5ldHlwZSA9IDQpICsgIyByZXNvbHV0aW9uX3BhcmFtZXRlciB2YXJlaXMsIHN0YXJ0IHRyeWluZyB3aXRoIDEuNQogICAgbGFicyh4ID0gInJlc29sdXRpb24gcGFyYW1ldGVyIiwgeSA9ICJudW0uIG1vZHVsZXNcbncvID49NSBnZW5lcyIpICsKICAgIHRoZW1lX2NsYXNzaWMoKSArIHRoZW1lKHRleHQgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDE0KSwgYXhpcy50ZXh0ID0gZWxlbWVudF90ZXh0KGNvbG9yID0gImJsYWNrIikpCgpPcHRpbWl6ZV9udW1fZ2VuZSA9IG9wdGltaXphdGlvbl9yZXN1bHRzICU+JQogICAgZ2dwbG90KGFlcyh4ID0gcmVzb2x1dGlvbiwgeSA9IG51bV9jb250YWluZWRfZ2VuZSkpICsKICAgIGdlb21fbGluZShzaXplID0gMS4xLCBhbHBoYSA9IDAuOCwgY29sb3IgPSAidmlvbGV0cmVkMiIpICsKICAgIGdlb21fcG9pbnQoc2l6ZSA9IDMsIGFscGhhID0gMC43KSArCiAgICBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQgPSAyLCBzaXplID0gMSwgbGluZXR5cGUgPSA0KSArCiAgICBsYWJzKHggPSAicmVzb2x1dGlvbiBwYXJhbWV0ZXIiLCB5ID0gIm51bS4gZ2VuZXMgaW5cbm1vZHVsZXMgdy8gPj01IGdlbmVzIikgKwogICAgdGhlbWVfY2xhc3NpYygpICsgdGhlbWUodGV4dCA9IGVsZW1lbnRfdGV4dChzaXplID0gMTQpLCBheGlzLnRleHQgPSBlbGVtZW50X3RleHQoY29sb3IgPSAiYmxhY2siKSkKCk9wdGltaXplX251bV9tb2R1bGUgLyBPcHRpbWl6ZV9udW1fZ2VuZQoKZ2dzYXZlKCJyZXNvbHV0aW9uLnN2ZyIsIGhlaWdodCA9IDUsIHdpZHRoID0gNiwgYmcgPSJ3aGl0ZSIpCmdnc2F2ZSgicmVzb2x1dGlvbi5wbmciLCBoZWlnaHQgPSA1LCB3aWR0aCA9IDYsIGJnID0id2hpdGUiKQpgYGAKCgpgYGB7cn0Kc2V0LnNlZWQoOTg3KQoKbW9kdWxlcyA9IGNsdXN0ZXJfbGVpZGVuKG15X25ldHdvcmssIHJlc29sdXRpb25fcGFyYW1ldGVyID0gMiwgb2JqZWN0aXZlX2Z1bmN0aW9uID0gIm1vZHVsYXJpdHkiKQoKbXlfbmV0d29ya19tb2R1bGVzID0gZGF0YS5mcmFtZSgKICAgIGdlbmUgPSBuYW1lcyhtZW1iZXJzaGlwKG1vZHVsZXMpKSwKICAgIG1vZHVsZSA9IGFzLnZlY3RvcihtZW1iZXJzaGlwKG1vZHVsZXMpKQogICkKCm15X25ldHdvcmtfbW9kdWxlcyAlPiUgCiAgZ3JvdXBfYnkobW9kdWxlKSAlPiUgCiAgY291bnQoKSAlPiUgCiAgYXJyYW5nZSgtbikgJT4lIAogIGZpbHRlcihuID49IDUpIApgYGAKCjEyIG1vZHVsZXMgY29udGFpbiBtb3JlIHRoYW4gNSBnZW5lcywgY292ZXJpbmcgMjU2MCBnZW5lcy4KCmBgYHtyfQpteV9uZXR3b3JrX21vZHVsZXMKYGBgCgojIERHRSBhbmFseXNpcwoKYGBge3J9CmFwcGxlX2V4cF9sb2dfd2lkZSA9IGFwcGxlX2V4cF9sb2dfbG9uZyAlPiUKICBkcGx5cjo6c2VsZWN0KGdlbmUsdGltZXBvaW50LGxvZ19leHApICU+JQogIHBpdm90X3dpZGVyKG5hbWVzX2Zyb20gPSB0aW1lcG9pbnQsIHZhbHVlc19mcm9tID0gbG9nX2V4cCkgJT4lCiAgY29sdW1uX3RvX3Jvd25hbWVzKCJnZW5lIikKCmhlYWQoYXBwbGVfZXhwX2xvZ193aWRlKQpgYGAKCmBgYHtyfQojIG1hbnVhbGx5IGNhbGN1bGF0ZSBsb2cyZmMKIyB0aGUgcmVhc29uIGZvciBub3QgdXNpbmcgREVTZXEyIGlzIHRoZXkgcmVxdWlyZSByYXcgY291bnQgKGludGVnZXIpIGlucHV0CiMgdGhlIHJlYXNvbiBmb3Igbm90IHVzaW5nIGxpbW1hIGlzIHRoZXkgcmVxdWlyZSBtdWx0aXBsZSByZXBsaWNhdGVzIGZvciBlYWNoIGNvbmRpdGlvbgoKbG9nMl9mYyA8LSBhcHBsZV9leHBfbG9nX3dpZGUgJT4lCiAgbXV0YXRlKAogICAgbG9nMkZDXzM1X3ZzXzYwID0gbG9nMihgMzUgREFBYCAvIGA2MCBEQUFgKSwgICMgY2VsbCBkaXZpc2lvbiBlbmQgdnMgc3RhcmNoIGFjY3VtdWxhdGlvbgogICAgbG9nMkZDXzg3X3ZzXzEzMiA9IGxvZzIoYDg3IERBQWAgLyBgMTMyIERBQWApLCAgIyBzdGFyY2ggYWNjdW11bGF0aW9uIHZzIHJpcGVuaW5nIAogICAgbG9nMkZDXzM1X3ZzXzEzMiA9IGxvZzIoYDM1IERBQWAgLyBgMTMyIERBQWApLCAgIyBjZWxsIGRpdmlzaW9uIHZzIHJpcGVuaW5nCiAgICBsb2cyRkNfNjBfdnNfODcgPSBsb2cyKGA2MCBEQUFgIC8gYDg3IERBQWApICAjIHN0YXJjaCBhY2N1bXVsYXRpb24gdnMgZW5kCiAgKQoKaGVhZChsb2cyX2ZjKQpgYGAKCmBgYHtyfQpyaXBlbmluZ19nZW5lcyA9IGxvZzJfZmMgJT4lCiAgZmlsdGVyKGxvZzJGQ18zNV92c18xMzIgPCAtMSAmIGxvZzJGQ184N192c18xMzIgPCAtMSApICU+JQogIHJvd25hbWVzKCkgIyBJbiBib3RoIGNvbXBhcmlzb24sIHNpZ25pZmljYW50bHkgdXByZWd1bGF0ZWQgaW4gcmlwZW5pbmcgc3RhZ2Vz77yM5Y+v6IO95LiO5p6c5a6e5oiQ54af55u45YWz44CC6L+Z5Lqb5Z+65Zug5Y+v6IO95Y+C5LiO6LCD5o6n5p6c5a6e6LSo5Zyw44CB6aOO5ZGz44CB6Imy5rO955qE5Y+Y5YyW44CCCgpzdGFyY2hfcmVnX2dlbmVzID0gbG9nMl9mYyAlPiUKICBmaWx0ZXIoYWJzKGxvZzJGQ182MF92c184NykgPiAxKSAlPiUgIyA+IDE6IDYwID4gODc7IDwgLTEgNjAgPCA4NwogIHJvd25hbWVzKCkKYGBgCgoKYGBge3J9CnNldC5zZWVkKDk4NykKCmxpYnJhcnkocGhlYXRtYXApCgpoZWF0bWFwX2RhdGFfcmlwZW4gPC0gYXBwbGVfZXhwX2xvZ193aWRlW3JpcGVuaW5nX2dlbmVzLCBdCgojIFNhdmUgYXMgU1ZHCiMgc3ZnbGl0ZSgiaGVhdG1hcF9yaXBlbmluZy5zdmciLCB3aWR0aCA9IDUsIGhlaWdodCA9IDYpCiMgaGVhdG1hcApwaGVhdG1hcCgKICBoZWF0bWFwX2RhdGFfcmlwZW4sIAogIGNsdXN0ZXJfcm93cyA9IFRSVUUsIAogIGNsdXN0ZXJfY29scyA9IEZBTFNFLCAKICBzaG93X3Jvd25hbWVzID0gVFJVRSwgCiAgc2NhbGUgPSAicm93IgopCiMgZGV2Lm9mZigpCmBgYAoKYGBge3J9CnNldC5zZWVkKDk4NykKCmxpYnJhcnkocGhlYXRtYXApCgpoZWF0bWFwX2RhdGFfc3RhcmNoIDwtIGFwcGxlX2V4cF9sb2dfd2lkZVtzdGFyY2hfcmVnX2dlbmVzLCBdCiMgc3ZnbGl0ZSgiaGVhdG1hcF9zdGFyY2guc3ZnIiwgd2lkdGggPSA1LCBoZWlnaHQgPSA2KQojIGhlYXRtYXAKcGhlYXRtYXAoCiAgaGVhdG1hcF9kYXRhX3N0YXJjaCwgCiAgY2x1c3Rlcl9yb3dzID0gVFJVRSwgCiAgY2x1c3Rlcl9jb2xzID0gRkFMU0UsIAogIHNob3dfcm93bmFtZXMgPSBGQUxTRSwgCiAgc2NhbGUgPSAicm93IgopCiMgZGV2Lm9mZigpCgojIG1hbnkgZ2VuZXMKYGBgCgpgYGB7cn0KcGhlYXRtYXAoCiAgYXBwbGVfZXhwX2xvZ193aWRlLAogIGNsdXN0ZXJfcm93cyA9IEZBTFNFLCAKICBjbHVzdGVyX2NvbHMgPSBGQUxTRSwgCiAgc2hvd19yb3duYW1lcyA9IEZBTFNFLCAKICBzY2FsZSA9ICJyb3ciCikKYGBgCgoKCgpgYGB7cixldmFsPUZBTFNFfQpzYW1wbGVfaW5mbyA9IGRhdGEuZnJhbWUoCiAgc2FtcGxlID0gY29sbmFtZXMoYXBwbGVfZXhwX2xvZ193aWRlKSwKICBncm91cCA9IGMoIlQwIiwgIlQxNCIsICJUMjUiLCAiVDM1IiwgIlQ2MCIsICJUODciLCAiVDEzMiIsICJUMTQ2IikKKQoKc2FtcGxlX2luZm8kZ3JvdXAgPC0gZmFjdG9yKHNhbXBsZV9pbmZvJGdyb3VwLCBsZXZlbHMgPSB1bmlxdWUoc2FtcGxlX2luZm8kZ3JvdXApKQpgYGAKCgojIEVucmljaG1lbnQgY2FjdWxhdGlvbiBmb3IgY2VydGFpbiBnZW5lcyAKYGBge3J9CiMgQ2FsY3VsYXRlIGVucmljaG1lbnQgZm9yIG1vZHVsZXMgZnVuY3Rpb24KIyBNYWtlIHN1cmUgeW91IGdvdCB0aGUgbW9kdWxlX2luZm9fZGYuCiMgVGhlIGZvcm1hdCBvZiBtb2R1bGUgaW5mbyBkZiBzaG91bGQgYmUgYSAyIGNvbHVtbnMgdGliYmxlIHdpdGggZmlyc3QgY29sdW1uIG5hbWVkIGdlbmUsIHNlY29uZCBjb2x1bW4gbmFtZWQgbW9kdWxlLgojIFRoZSBpbnRlcmVzdGluZ19nZW5lcyBwYXJ0IHNob3VsZCBiZSBhIHNldCBvZiBnZW5lcy4KY2FsY3VsYXRlX21vZHVsZV9lbnJpY2htZW50ID0gZnVuY3Rpb24obW9kdWxlX2luZm9fZGYsIGludGVyZXN0aW5nX2dlbmVzKSB7CiAgbGlicmFyeShicm9vbSkKICBsaWJyYXJ5KHRpZHl2ZXJzZSkKCiAgZGYxID0gbW9kdWxlX2luZm9fZGYgJT4lCiAgICBncm91cF9ieShtb2R1bGUpICU+JQogICAgc3VtbWFyaXNlKAogICAgICByYXdfY291bnQgPSBuKCksCiAgICApICU+JQogICAgdW5ncm91cCgpCiAgCiAgZGYyID0gZGF0YS5mcmFtZShnZW5lID0gaW50ZXJlc3RpbmdfZ2VuZXMpICU+JQogICAgbGVmdF9qb2luKG1vZHVsZV9pbmZvX2RmLCBieSA9ICJnZW5lIikgJT4lCiAgICBncm91cF9ieShtb2R1bGUpICU+JQogICAgc3VtbWFyaXNlKAogICAgICByYXdfY291bnQgPSBuKCksCiAgICApICU+JQogICAgdW5ncm91cCgpCiAgCiAgYWxsX21vZHVsZXMgPSB1bmlxdWUoZGYxJG1vZHVsZSkKCiAgcmVzdWx0X2RmID0gdGliYmxlKCkgCiAgCiAgZm9yIChpIGluIDE6bGVuZ3RoKGFsbF9tb2R1bGVzKSkgewogICAgbW9kdWxlID0gYWxsX21vZHVsZXNbaV0KCiAgICByYXdfY291bnRfZGYxID0gZGYxW2RmMSRtb2R1bGUgPT0gbW9kdWxlLCAicmF3X2NvdW50Il0KICAgIHJhd19jb3VudF9kZjIgPSBuYS5vbWl0KGRmMltkZjIkbW9kdWxlID09IG1vZHVsZSwgInJhd19jb3VudCJdKQoKICAgIGlmIChucm93KHJhd19jb3VudF9kZjIpID09IDApIHsKICAgICAgICByYXdfY291bnRfZGYyID0gMAogICAgfQogICAgdG90YWxfY291bnRfZGYxID0gc3VtKGRmMSRyYXdfY291bnQpCiAgICB0b3RhbF9jb3VudF9kZjIgPSBzdW0oZGYyJHJhd19jb3VudCkKICAgIAogICAgY29udGluZ2VuY3lfdGFibGUgPSBtYXRyaXgoYyhzdW0ocmF3X2NvdW50X2RmMiksIAogICAgICAgICBzdW0ocmF3X2NvdW50X2RmMSksIAogICAgICAgICB0b3RhbF9jb3VudF9kZjIgLSBzdW0ocmF3X2NvdW50X2RmMiksCiAgICAgICAgIHRvdGFsX2NvdW50X2RmMSAtIHN1bShyYXdfY291bnRfZGYxKSksCiAgICAgICBucm93ID0gMikKICAgIAogICAgZmlzaGVyX3Rlc3RfcmVzdWx0ID0gdGlkeShmaXNoZXIudGVzdChjb250aW5nZW5jeV90YWJsZSwgYWx0ZXJuYXRpdmUgPSAiZyIpKQogICAgCiAgICBwZXJjZW50YWdlID0gcGFzdGUocm91bmQoc3VtKHJhd19jb3VudF9kZjIpIC8gdG90YWxfY291bnRfZGYyICogMTAwLCAyKSwgIiUgLyAiLCAKICAgICAgICAgICAgICAgICAgICAgICAgcm91bmQoc3VtKHJhd19jb3VudF9kZjEpIC8gdG90YWxfY291bnRfZGYxICogMTAwLCAyKSwgIiUiLCBzZXAgPSAiIikKICAgIAogICAgdGVtcF9kZiA9IHRpYmJsZShtb2R1bGUgPSBtb2R1bGUsCiAgICAgICAgICAgICAgICAgICAgIHJhd19jb3VudCA9IHBhc3RlKHJhd19jb3VudF9kZjIsIHJhd19jb3VudF9kZjEsIHNlcCA9ICIvIiksCiAgICAgICAgICAgICAgICAgICAgIHBlcmNlbnRhZ2UgPSBwZXJjZW50YWdlKQogICAgCiAgICB0ZW1wX2RmID0gYmluZF9jb2xzKHRlbXBfZGYsIGZpc2hlcl90ZXN0X3Jlc3VsdCkgCiAgICByZXN1bHRfZGYgPSBiaW5kX3Jvd3MocmVzdWx0X2RmLCB0ZW1wX2RmKSAKICB9CiAgCiAgcmV0dXJuKHJlc3VsdF9kZikKfQpgYGAKCgoKIyBNb2R1bGUgdHJlbmRzCgpgYGB7cn0KbW9kdWxlX21lYW5zIDwtIGFwcGxlX2V4cF9sb2dfaGlnaHZhciAlPiUKICBkcGx5cjo6c2VsZWN0KGdlbmUsIHRpbWVwb2ludCwgbG9nX2V4cCkgJT4lCiAgbGVmdF9qb2luKG15X25ldHdvcmtfbW9kdWxlcywgYnkgPSBqb2luX2J5KGdlbmUpKSAlPiUKICBmaWx0ZXIobW9kdWxlICVpbiUgYygzLCAyLCA0LCA3LCA5KSkgJT4lCiAgbXV0YXRlKHRpbWVwb2ludCA9IGZhY3Rvcih0aW1lcG9pbnQsIGxldmVscyA9IGMoIjAgREFBIiwgIjE0IERBQSIsICIyNSBEQUEiLCAiMzUgREFBIiwgIjYwIERBQSIsICI4NyBEQUEiLCAiMTMyIERBQSIsICIxNDYgREFBIikpKSAlPiUKICBncm91cF9ieShtb2R1bGUsdGltZXBvaW50KSAlPiUKICBzdW1tYXJpc2UobWVhbl9leHAgPSBtZWFuKGxvZ19leHAsIG5hLnJtID0gVFJVRSksIC5ncm91cHMgPSAiZHJvcCIpCgphcHBsZV9leHBfbG9nX2hpZ2h2YXIgJT4lCiAgZHBseXI6OnNlbGVjdChnZW5lLCB0aW1lcG9pbnQsIGxvZ19leHApICU+JQogIGxlZnRfam9pbihteV9uZXR3b3JrX21vZHVsZXMsIGJ5ID0gam9pbl9ieShnZW5lKSkgJT4lCiAgZmlsdGVyKG1vZHVsZSAlaW4lIGMoMywgMiwgNCwgNywgOSkpICU+JQogIG11dGF0ZSh0aW1lcG9pbnQgPSBmYWN0b3IodGltZXBvaW50LCBsZXZlbHMgPSBjKCIwIERBQSIsICIxNCBEQUEiLCAiMjUgREFBIiwgIjM1IERBQSIsICI2MCBEQUEiLCAiODcgREFBIiwgIjEzMiBEQUEiLCAiMTQ2IERBQSIpKSkgJT4lCiAgZ2dwbG90KGFlcyh4ID0gdGltZXBvaW50LCB5ID0gbG9nX2V4cCwgZ3JvdXAgPSBnZW5lKSkgKwogICMg5aSn6YOo5YiG5Z+65Zug54Gw6Imy57q/CiAgZ2VvbV9saW5lKGNvbG9yID0gImdyZXkiLCBhbHBoYSA9IDAuNSkgKwogICMg5re75Yqg5qih5Z2X5Z2H5YC857KX57q/CiAgZ2VvbV9saW5lKGRhdGEgPSBtb2R1bGVfbWVhbnMsIGFlcyh4ID0gdGltZXBvaW50LCB5ID0gbWVhbl9leHAsIGNvbG9yID0gYXMuZmFjdG9yKG1vZHVsZSksIGdyb3VwID0gbW9kdWxlKSwgc2l6ZSA9IDEuNSkgKwogIHRoZW1lX21pbmltYWwoKSArCiAgbGFicygKICAgIHRpdGxlID0gIkV4cHJlc3Npb24gVHJlbmRzIGZvciBTZWxlY3RlZCBNb2R1bGVzIiwKICAgIHggPSAiVGltZXBvaW50IChEQUEpIiwKICAgIHkgPSAiRXhwcmVzc2lvbiBWYWx1ZSIsCiAgICBjb2xvciA9ICJNb2R1bGUiCiAgKSArCiAgZmFjZXRfd3JhcCh+bW9kdWxlLCBzY2FsZXMgPSAiZnJlZV95IikgKyAgIyDliIbpnaLlsZXnpLrmr4/kuKrmqKHlnZcKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpKQoKZ2dzYXZlKCJleHByZXNzaW9uX3RyZW5kc190b3BfbW9kdWxlcy5zdmciLCBoZWlnaHQgPSA2LCB3aWR0aCA9MTAsIGJnID0id2hpdGUiKQpnZ3NhdmUoImV4cHJlc3Npb25fdHJlbmRzX3RvcF9tb2R1bGVzLnBuZyIsIGhlaWdodCA9IDYsIHdpZHRoID0xMCwgYmcgPSJ3aGl0ZSIpCmBgYAoKYGBge3J9CmNhbGN1bGF0ZV9tb2R1bGVfZW5yaWNobWVudChteV9uZXR3b3JrX21vZHVsZXMscmlwZW5pbmdfZ2VuZXMpCmBgYAoKYGBge3J9CmFwcGxlX2V4cF9sb2dfaGlnaHZhciAlPiUKICBkcGx5cjo6c2VsZWN0KGdlbmUsIHRpbWVwb2ludCwgbG9nX2V4cCkgJT4lCiAgbGVmdF9qb2luKG15X25ldHdvcmtfbW9kdWxlcywgYnkgPSBqb2luX2J5KGdlbmUpKSAlPiUKICBmaWx0ZXIoZ2VuZSAlaW4lIHJpcGVuaW5nX2dlbmVzKSAlPiUKICBtdXRhdGUodGltZXBvaW50ID0gZmFjdG9yKHRpbWVwb2ludCwgbGV2ZWxzID0gYygiMCBEQUEiLCAiMTQgREFBIiwgIjI1IERBQSIsICIzNSBEQUEiLCAiNjAgREFBIiwgIjg3IERBQSIsICIxMzIgREFBIiwgIjE0NiBEQUEiKSkpICU+JQogIGdncGxvdChhZXMoeCA9IHRpbWVwb2ludCwgeSA9IGxvZ19leHAsIGdyb3VwID0gZ2VuZSwgY29sb3IgPSBnZW5lKSkgKwogIGdlb21fcG9pbnQoKSArCiAgZ2VvbV9saW5lKHNpemUgPSAwLjUsIGFscGhhID0gMC44KSArCiAgdGhlbWVfbWluaW1hbCgpICsKICBsYWJzKAogICAgdGl0bGUgPSAiRXhwcmVzc2lvbiBUcmVuZHMgb2YgUmlwZW5pbmcgUHJvYmVzIiwKICAgIHggPSAiVGltZXBvaW50IChEQUEpIiwKICAgIHkgPSAiTG9nIEV4cHJlc3Npb24gVmFsdWUiLAogICAgY29sb3IgPSAiR2VuZSIKICApICsKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpLGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikKCmdnc2F2ZSgiZXhwcmVzc2lvbl90cmVuZHNfcmlwZW5pbmdfbG9nLnN2ZyIsIGhlaWdodCA9IDUsIHdpZHRoID0gNywgYmcgPSJ3aGl0ZSIpCmdnc2F2ZSgiZXhwcmVzc2lvbl90cmVuZHNfcmlwZW5pbmdfbG9nLnBuZyIsIGhlaWdodCA9IDUsIHdpZHRoID0gNywgYmcgPSJ3aGl0ZSIpCmBgYAoKCmBgYHtyfQphcHBsZV9leHAgJT4lCiAgZ3JvdXBfYnkoYEdlbmJhbmsgbnVtYmVyYCkgJT4lCiAgbXV0YXRlKGdlbmUgPSBjYXNlX3doZW4oICMgbXV0YXRlIGEgbmV3IGNvbCAKICAgIG4oKSA+IDEgfiBwYXN0ZTAoYEdlbmJhbmsgbnVtYmVyYCwgIl8iLCByb3dfbnVtYmVyKCkpLCAjICJkdXBsaWNhdGVzIiB3aWxsIGhhdmUgbW9yZSB0aGFuIDEgcm93LiBBZGQgYSBzeW1ib2wuCiAgICBUUlVFIH4gYEdlbmJhbmsgbnVtYmVyYCAjIGtlZXAgdGhlIHNhbWUgZm9yIHRob3NlIG5vdF9kdXBsaWNhdGVkCiAgKSkgJT4lCiAgdW5ncm91cCgpICU+JQogIHJlbmFtZV93aXRoKH4gZ3N1YigiIE1lYW4iLCAiIiwgLiksIGNvbnRhaW5zKCJEQUEiKSkgJT4lICMgUmVtb3ZlIHRoZSAiTWVhbiIgaW4gY29sbmFtZXMKICBwaXZvdF9sb25nZXIoCiAgICBjb2xzID0gc3RhcnRzX3dpdGgoIjAgREFBIik6c3RhcnRzX3dpdGgoIjE0NiBEQUEiKSwgCiAgICBuYW1lc190byA9ICJ0aW1lcG9pbnQiLCAKICAgIHZhbHVlc190byA9ICJleHByZXNzaW9uIgogICkgJT4lCiAgZHBseXI6OnNlbGVjdChnZW5lLCB0aW1lcG9pbnQsIGV4cHJlc3Npb24pICU+JQogIGxlZnRfam9pbihteV9uZXR3b3JrX21vZHVsZXMsIGJ5ID0gam9pbl9ieShnZW5lKSkgJT4lCiAgZmlsdGVyKGdlbmUgJWluJSByaXBlbmluZ19nZW5lcykgJT4lCiAgbXV0YXRlKHRpbWVwb2ludCA9IGZhY3Rvcih0aW1lcG9pbnQsIGxldmVscyA9IGMoIjAgREFBIiwgIjE0IERBQSIsICIyNSBEQUEiLCAiMzUgREFBIiwgIjYwIERBQSIsICI4NyBEQUEiLCAiMTMyIERBQSIsICIxNDYgREFBIikpKSAlPiUKICBnZ3Bsb3QoYWVzKHggPSB0aW1lcG9pbnQsIHkgPSBleHByZXNzaW9uLCBncm91cCA9IGdlbmUsIGNvbG9yID0gZ2VuZSkpICsKICBnZW9tX3BvaW50KCkgKwogIGdlb21fbGluZShzaXplID0gMC41LCBhbHBoYSA9IDAuOCkgKwogIHRoZW1lX21pbmltYWwoKSArCiAgbGFicygKICAgIHRpdGxlID0gIkV4cHJlc3Npb24gVHJlbmRzIG9mIFJpcGVuaW5nIFByb2JlcyIsCiAgICB4ID0gIlRpbWVwb2ludCAoREFBKSIsCiAgICB5ID0gIkV4cHJlc3Npb24gVmFsdWUiLAogICAgY29sb3IgPSAiR2VuZSIKICApICsKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpLGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikKCmdnc2F2ZSgiZXhwcmVzc2lvbl90cmVuZHNfcmlwZW5pbmcuc3ZnIiwgaGVpZ2h0ID0gNSwgd2lkdGggPSA3LCBiZyA9IndoaXRlIikKZ2dzYXZlKCJleHByZXNzaW9uX3RyZW5kc19yaXBlbmluZy5wbmciLCBoZWlnaHQgPSA1LCB3aWR0aCA9IDcsIGJnID0id2hpdGUiKQpgYGAKCmBgYHtyfQpjYWxjdWxhdGVfbW9kdWxlX2VucmljaG1lbnQobXlfbmV0d29ya19tb2R1bGVzLGJhaXRfZ2VuZXMpCmBgYAoKU2VlbXMgbGlrZSB0aGUgQ0RLQjIgYW5kIENLUzEgaG9tb2xvZ3VlcyBhcmUgYXNzaWduZWQgKGNsdXN0ZXJlZCkgdG9nZXRoZXIgYmFzZWQgb24gdGhlIHRyZW5kIHdoaWxlIENES0IxIGlzIHNlcGFyYXRlZC4KCmBgYHtyfQpjYWxjdWxhdGVfbW9kdWxlX2VucmljaG1lbnQobXlfbmV0d29ya19tb2R1bGVzLHN0YXJjaF9yZWdfZ2VuZXMpCmBgYAoK